画像(logo)

HOME/[C言語DXライブラリ]STGの作り方 目次/十八日目 敵の動きを追加2

広告

[C言語 DXライブラリ STGの作り方]
十八日目 敵の動きを追加2

広告

↓2016年02月29日発売↓

12歳からはじめる ゼロからの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の角度を上げていったプログラムです。

画像(cdxs_18_1)

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

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

画像(cdxs_18_2)

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

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

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

Sin波を使ってみよう

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

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

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

Sin値利用プログラム

画像(cdxs_18_3)

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

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

プログラムを見てみると

x = sin(angle2) * 200;

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

x = sin(angle2) * 200 + 320;

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

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

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

敵の動きを追加2

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

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

画像(cdxs_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 11」です。

画像(cdxs_18_5)

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

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

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

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

画像(cdxs_18_6)

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

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

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

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

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

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

y = 0.02 * ((x - 100) * (x - 100)) + 100;
画像(cdxs_18_7)

こうなります。

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

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

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

y = 0.02 * ((x + 100) * (x + 100)) + 100;
画像(cdxs_18_8)

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

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

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

y = -0.02 * ((x + 100) * (x + 100)) + 100;
画像(cdxs_18_9)

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

初期化部分

if (enemy[i].init_flag == 0){
	enemy[i].x = -220;
	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];

頂点座標(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 13」です。

画像(cdxs_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 14」です。

画像(cdxs_18_11)

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

初期化部分

if (enemy[i].init_flag == 0){
	enemy[i].x = -220;
	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{
	/*移動部分*/
}

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

移動部分

if (gamecount < enemy[i].gamecount_point + 50){
	enemy[i].x++;
	enemy[i].y = 0.1 * ((enemy[i].x + 220) * (enemy[i].x + 220)) - 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 16」です。

画像(cdxs_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);
}
if (gamecount > enemy[i].gamecount_point + 1000){
	enemy[i].y--;
}

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

次の動きにいきます。

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

画像(cdxs_18_13)

上から降りてきてしばらく動き回り、途中で再び戻ります。

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

画像からは意味不明だと思いますので実際に実行してお確かめください。

Sin波利用法2

動きの説明に入る前にこの動きで使われているもう一つのSin波の利用法を解説いたします。

こちらのグラフをご覧ください。

画像(cdxs_18_14)

最初にも紹介しました「Sin」波のグラフになります。

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

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

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

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

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

Sin値表示プログラム2

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

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

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

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

画像(cdxs_18_15)

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

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

画像(cdxs_18_16)

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

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

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

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

画像(cdxs_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].x = enemy[i].first_x;
	enemy[i].y = 280;
	enemy[i].var[0] = 0;
	enemy[i].gamecount_point = gamecount;
	enemy[i].init_flag = 1;
}
else{
	/*移動部分*/
}

初期化部分は画面上部から登場するのでその座標をセットしているのと再び変数をいくつか使うのでその準備をします。

移動部分

今回の動きは大きく分けて「登場」「画面内移動」「退散」の3部分に分かれております。

まずは登場部分です。

if (gamecount < enemy[i].gamecount_point + 100){
	enemy[i].y = 280 - (sin(DX_PI / 100 * enemy[i].var[0] - (DX_PI * 0.5f)) + 1) / 2 * 220;
	enemy[i].var[0]++;
}

つい先ほど説明しました「Sin」の値の変化を利用した動きですね。

カウント用の変数として「enemy[i].var[0]」というのを使っております。

これらを使ってy座標を「280から60」まで滑らかに移動するという動きを実現します。

次に「画面内移動」の部分です。

プログラムを見ると長ったらしい上にややこしいのですが、やっている事は縦、横、斜めいずれかの方向に滑らかに移動するというだけの事なので蓋をあけてみればけっこう単純です。

一部分だけ抜き出して見てみましょう。

enemy[i].x = enemy[i].var[1] + ((sin(DX_PI / 50 * enemy[i].var[0] - (DX_PI * 0.5f)) + 1) / 2 * 80);
enemy[i].y = enemy[i].var[2] + ((sin(DX_PI / 50 * enemy[i].var[0] - (DX_PI * 0.5f)) + 1) / 2 * 80);

いくつかポイントがあるのですが、まず前までいた場所からの移動になりますのでその場所を保存するのに

/*x座標*/
enemy[i].var[1]
/*y座標*/
enemy[i].var[2]

という2つの変数を使っております。

あとは先ほどと同じようにカウント用に「enemy[i].var[0]」という変数を使って「50」カウントで「0から80」を滑らかに変化という値を作りそれぞれの方向に合わせて座標に足し合わせているだけです。

そして一定時間で移動方向を切り替える部分と、画面端に行ってしまったらもとに戻るようにする処理を加えれば「画面内移動」の部分は完成です。

最後に「退散」の部分

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

こちらを加えれば今回の動きも完成ですね。

加速度、摩擦などを考慮した動き

最後に加速度、摩擦などを考慮した動きの作り方を軽く紹介したいと思います。

実際の物理法則に則ったものではないのですが上手に使えばさらに多彩な動きが表現できるようになると思いますのでぜひこの機会に覚えておいてください。

それでは使い方ですがまず事前準備として「移動量」・「加速度」・「摩擦係数」を表す変数を追加で準備します。

/*移動量*/
double m_x,m_y;
/*加速度*/
double a_x,a_y;
/*摩擦係数*/
double f_x,f_y;

こんな感じで「x方向」・「y方向」それぞれの分を用意しました。

ではこれらを使って計算していきます。

次のプログラムをその内容を確認しつつ実行してみてください。

加速・摩擦などを利用したプログラム

画像(cdxs_18_18)

画像からはほぼ何も伝わりませんが、プログラムの中身の「my_move_ball()」に注目です。

void my_move_point(){
	/*座標に移動量を足し合わせる部分*/
	x = x + m_x;
	y = y + m_y;

	/*移動量に加速度を足し合わせる部分*/
	m_x = m_x + a_x;
	m_y = m_y + a_y;
}

コメントにも入れておきましたが基本はこの考え方で物体の座標を移動させます。

その1・座標に移動量を足し合わせる

その2・移動量に加速度を足し合わせる

今のトコロは何のこっちゃなので実際に値を調整して試してみましょう。

void my_init_point(){
	x = -220;
	y = 0;
	draw_x = 0;
	draw_y = 0;

	/*移動量の初期化*/
	m_x = 1;
	m_y = 0;

	/*加速度の初期化*/
	a_x = 0.1;
	a_y = 0;
}

x方向の加速度を「0.1」にしてみました。

画像(cdxs_18_19)

いかがでしょうか?

毎回速度に加速度の値が加算される事になるのでその結果、物体が加速しながら移動する事になったと思います。

加えてy方向の加速度なども変化させる事によりさらに変化に富んだ動きが表現できるかと思います。

注意点としてはそのままだとひたすら加算され続けるので「if」文などを使い定めた最大速度で加算を止めるなどの工夫が必要です。

void my_move_point(){
	/*座標に移動量を足し合わせる部分*/
	x = x + m_x;
	y = y + m_y;

	/*移動量に加速度を足し合わせる部分*/
	if(m_x < 5){
		m_x = m_x + a_x;
	}
	if(m_y < 5){
		m_y = m_y + a_y;
	}	
}

続きまして摩擦係数を設定する事により徐々に速度を落としていくようなプログラムです。

「my_init_point()」と「my_move_point()」を以下のように変更してみてください。

void my_init_point(){
	x = -220;
	y = 0;
	draw_x = 0;
	draw_y = 0;

	/*移動量の初期化*/
	m_x = 5;
	m_y = 0;

	/*摩擦係数の初期化*/
	f_x = 0.9;
	f_y = 0;
}

void my_move_point(){
	/*座標に移動量を足し合わせる部分*/
	x = x + m_x;
	y = y + m_y;

	/*移動量に摩擦係数を掛け合わせる部分*/
	m_x *= f_x;
	m_y *= f_y;
}

基本はこちらになります。

今度は移動量に摩擦係数を掛け合わせているのに注意です。

画像(cdxs_18_20)

画面はじですぐに止まってしまうのでわかりづらいかもしれませんが、滑り止めの上に乗ってしまったかのように停止されるのが確認できたかと思います。

もしどうしてもわかりづらいという場合は

void my_move_point(){
	/*座標に移動量を足し合わせる部分*/
	x = x + m_x;
	y = y + m_y;

	/*移動量に摩擦係数を掛け合わせる部分*/
	if(x > 0){
		m_x *= f_x;
		m_y *= f_y;
	}
}

みたいな条件を簡単に加えてx座標を少し動かした上で試してみてください。

これはある数に対して「1」以下の数を乗算し続けると「0」に近づいていくという計算のテクニックを使ったもので「1」に近ければ近いほど少しずつ「0」に近づいていく値が得られるというものになります。

摩擦係数「0.9」の場合

画像(cdxs_18_21)

摩擦係数「0.5」の場合

画像(cdxs_18_22)

こちらも上手に使えばよりリアルな停止を表現できると思いますのでいろいろと値を変えて試してみてください。

尚、これらを少しアレンジしたりして作った動きを「my_move_enemy()」の「case 18〜case 20」あたりに作ってありますのでそちらも参考にしてみてください!

画像(cdxs_18_23)

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

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

中間ソース17

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

次回

十九日目 バリアーとボム

□ページの先頭へ□

□目次へ戻る□

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