ゲームプログラムめも日記 このページをアンテナに追加 RSSフィード


けんもほろろ  / オススメゲームプログラム本  / 作ったもの置き場  
ゲーム開発のデザパタまとめ  / めも日記まとめ  

2006-02-25

[]えぐぜりにゃ〜ソース解説3 えぐぜりにゃ〜ソース解説3を含むブックマーク

えぐぜりにゃ〜ソース解説の続きです。

(※バージョンは「exelinya_0214.zip」です)

 

今回は敵の移動関数の解説になります。

 

移動関数概要

ソースとしては、801〜1029行目が、敵の移動関数になります。

 

 

803行目は、存在チェックですね。

		if(enemy_type(cnt) == ENEMY_TYPE_NULL) : continue

敵が存在していなければ、何もしません。

 

807行目は、画面外に出てしまった敵を削除しています。

		if(enemy_x(cnt) < -128.0 | enemy_y(cnt) < -128.0 | enemy_y(cnt) > 480.0 + 128.0) : enemy_type(cnt) == ENEMY_TYPE_NULL

 

809〜811行目では、距離・ラジアンを求めています。

		// 敵からプレイヤーまでの距離を求める
		vx = mychar_x - enemy_x(cnt)
		vy = mychar_y - enemy_y(cnt)
		// ラジアンを取得
		rr = atan(vy , vx)

 

準備は終わりました!

ここから、ようやく敵の移動です。

 

 

まずは弾です。

まあ、、単純な直線移動です。

		if((enemy_type(cnt) == ENEMY_TYPE_SHOT1) | (enemy_type(cnt) == ENEMY_TYPE_SHOT2)){
			// 直線移動
			enemy_x(cnt) += enemy_sx(cnt)
			enemy_y(cnt) += enemy_sy(cnt)
			// 画面外に出たら消える
			if(enemy_x(cnt) < -32.0 | enemy_x(cnt) > 640.0 + 32.0 | enemy_y(cnt) < -32.0 | enemy_y(cnt) > 480.0f + 32.0) : enemy_type(cnt) == ENEMY_TYPE_NULL
		}

 

 

ポッキー&大根

つづいて、ポッキー&大根です。(820〜837)

		if((enemy_type(cnt) == ENEMY_TYPE_SHOT3) | (enemy_type(cnt) == ENEMY_TYPE_SHOT4)){
			enemy_timer(cnt)++ // タイマー増加
			if(enemy_timer(cnt) < 40){
				// タイマーが0〜39の場合:回転するだけ
				enemy_rotate(cnt) += 0.3
			}else{
				// タイマーが40〜:
				if(enemy_timer(cnt) == 40){
					// タイマーが40のとき、プレイヤーの方向を見る
					enemy_rotate(cnt) = rr
				}
				// レベルに応じたスピードを算出
				sp = double(level) * 0.01 + 0.2
				// 向いている方向に移動量を足しこむ
				enemy_sx(cnt) += sp * cos(enemy_rotate(cnt))
				enemy_sy(cnt) += sp * sin(enemy_rotate(cnt))
			}
			// 速度減衰
			enemy_sx(cnt) *= 0.95
			enemy_sy(cnt) *= 0.95
			// 座標更新
			enemy_x(cnt) += enemy_sx(cnt)
			enemy_y(cnt) += enemy_sy(cnt)
			// 画面外に出たら消える
			if(enemy_x(cnt) < -32.0 | enemy_x(cnt) > 640.0 + 32.0 | enemy_y(cnt) < -32.0 | enemy_y(cnt) > 480.0f + 32.0) : enemy_type(cnt) == ENEMY_TYPE_NULL
		}

大体このような動きをします。

f:id:kenmo:20060224185208p:image

 

この一連の動作を切り替えるために、

ポッキー&大根では、タイマーを使っています。

「enemy_timer」がタイマー変数です。

 

「タイマー」と「行動」を表にするとこんな感じです。

タイマ行動
0〜39 回転→直線運動
40 プレイヤーの方向を向く→直線運動
41〜 向いている方向に進む→直線運動

 

<0〜39>

後で説明しますが、ポッキー&大根は、プリンが「適当な方向」に発射する特殊な弾です。

この段階では、その「適当な方向」に回転しながら進むだけです。

 

<40>

この瞬間だけ、プレイヤーの方向を向きます。

 

<41〜>

そして、向いている方向にひたすら進みます。

そのため、「プレイヤーをひたすら追いかける」という

極端にイヤラシイ動きをしないようになっています。

 

それについては「readme_j.txt」にこのような書き方をしています。

 上記のように、「掴み」状態にならないと攻撃が出来ないため、

 自機狙い弾=「通常」モードへ切り替え強制 である。

 このため、自機狙い弾はレベルデザイン上、使いどころが難しい。

えぐぜりにゃ〜では、自機狙い弾は回避手段を限定されるため、

このように、「ゆるい」自機狙い弾になっているかと思われます。

 

 

なす

839〜889行目はなすです。

少し凝った動きをしております。

 

		if(enemy_type(cnt) == ENEMY_TYPE_ZAKO1){
			enemy_timer(cnt)++
			if(enemy_timer(cnt) < 60){
				// 0〜59:減速しながら直線移動
				enemy_x(cnt) += enemy_sx(cnt)
				enemy_y(cnt) += enemy_sy(cnt)
				enemy_sx(cnt) *= 0.95
				enemy_sy(cnt) *= 0.95
			}
			if(enemy_timer(cnt) == 60){
				// 60:
				if(enemy_y(cnt) < 120.0 | enemy_y(cnt) > 360.0){
					// 上下の端っこ
					if(enemy_x(cnt) < 320.0){
						// 左寄りなので右に動く
						enemy_sx(cnt) = 1.0
						enemy_sy(cnt) = 0.0
					}else{
						// 右寄りなので左に動く
						enemy_sx(cnt) = -1.0
						enemy_sy(cnt) = 0.0
					}
				}else{
					// 中寄り
					if(enemy_y(cnt) < 240.0){
						// 上寄りなので下に移動
						enemy_sx(cnt) = 0.0
						enemy_sy(cnt) = 1.0
					}else{
						// 下寄りなので上移動
						enemy_sx(cnt) = 0.0
						enemy_sy(cnt) = -1.0
					}
				}
			}
			if(enemy_timer(cnt) > 60 & enemy_timer(cnt) < 310){
				// 61〜309:
				// 直線移動
				enemy_x(cnt) += enemy_sx(cnt)
				enemy_y(cnt) += enemy_sy(cnt)
				// 左回りをする
				enemy_x(cnt) += sin(double(enemy_timer(cnt)) * 0.1)
				enemy_y(cnt) += cos(double(enemy_timer(cnt)) * 0.1)

				if(enemy_timer(cnt) > 310 - level * 5){
					// レベルが高いと、
					enemy_rotate(cnt) += 0.1
					if(enemy_timer(cnt) \ 10 == 0){
						// 16方向のいずれかに、
						rr = double(rnd(16)) / 8.0 * MATH_PI
						// 弾発射
						addenemy enemy_x(cnt) , enemy_y(cnt) ,  4.0 * cos(rr) , 4.0 * sin(rr) , rr ,ENEMY_TYPE_SHOT1
					}
				}
			}
			if(enemy_timer(cnt) > 309){
				// 310〜:加速しながら画面外に出る
				// 画面中央までのベクトルを求める
				vx = 320.0 - enemy_x(cnt)
				vy = 240.0 - enemy_y(cnt)
				rr = sqrt(vx * vx + vy * vy)
				// 反転して足しこむ
				enemy_sx(cnt) = -vx / rr * double(enemy_timer(cnt) - 310) * 0.1
				enemy_sy(cnt) = -vy / rr * double(enemy_timer(cnt) - 310) * 0.1
				enemy_x(cnt) += enemy_sx(cnt)
				enemy_y(cnt) += enemy_sy(cnt)
			}
		}

 

大体このような動きをします。

f:id:kenmo:20060224185045p:image

 

「タイマー」と「行動」です

タイマ行動
0〜59 減速しながら画面内に入ってくる
60 画面外にでないような移動量を設定する
61〜309左回転をする
310〜 画面外に出て行く

 

kenmoの注目ポイントは「60」のところですね。

うまく画面外にでないような移動量を設定しています。

 

kenmoは昔、

「画面外に敵を叩き落す」という、

相撲のようなゲームを作ったことがあるのですが、

そのとき、「敵が画面外に移動しないようにする」アルゴリズムに苦労したことがあります。

(結局、いいアイデアが思いつかず、敵がいつも画面端に行ってしまうものになってしまいましたが…)

 

このようにうまく移動量を設定すればよかったのですね〜、、。

 

 

そして、「61〜309」で、直線移動+回転移動という2つの動きを組み合わせて、

なんとも不思議な動きをするのも、非常に面白いです。

 

 

たこ焼き

891〜907行目がたこ焼きです。

		if(enemy_type(cnt) == ENEMY_TYPE_ZAKO2){
			enemy_timer(cnt)++
			if(enemy_timer(cnt) \ 120 == 0){
				// 120での剰余が0:自機を狙う移動量を設定
				vx = mychar_x - enemy_x(cnt)
				vy = mychar_y - enemy_y(cnt)
				rr = sqrt(vx * vx + vy * vy)
				enemy_sx(cnt) = vx / rr * double(level + 48) / 8.0
				enemy_sy(cnt) = vy / rr * double(level + 48) / 8.0
			}else{
				// 20での剰余が0でない:ぐるぐる回るながら減速
				rr = double(enemy_timer(cnt) \ 120) / 120.0
				enemy_x(cnt) += enemy_sx(cnt)
				enemy_y(cnt) += enemy_sy(cnt)
				enemy_sx(cnt) *= 0.95
				enemy_sy(cnt) *= 0.95
				enemy_rotate(cnt) += (rr * rr)
			}
		}

動きとしては、このようになります。

f:id:kenmo:20060224185255p:image

 

1フレームだけ自機を狙う動きをします。

自機を狙う直前には、回転速度がかなり速くなっており、

いかにも、「いまから狙うぞ!」

という兆候が感じられます。

 

簡潔ながらも、プログラム的にもゲームデザイン的にも優れたアルゴリズムですね。

 

 

5箱とX箱

単なる直線移動なので、省略します(´∀`;

 

 

牛乳

914〜961行目は牛乳です。

		if(enemy_type(cnt) == ENEMY_TYPE_MID1){
			enemy_timer(cnt)++
			if(enemy_timer(cnt) < 60){
				// 〜59:減速直線移動・ショットの種類決め
				enemy_x(cnt) += enemy_sx(cnt)
				enemy_y(cnt) += enemy_sy(cnt)
				enemy_sx(cnt) *= 0.95
				enemy_sy(cnt) *= 0.95
				enemy_flag(cnt) = rnd(8) // ※ 0」が4way「4」がにんじんなので、1秒(30FPS)の間に25%の確率で発射
			}
			if(enemy_timer(cnt) > 59){
				// 60〜:左回り・画面外に出ないように移動量を設定
				enemy_sx(cnt) = 0.0
				enemy_sy(cnt) = 0.0
				enemy_x(cnt) += sin(double(enemy_timer(cnt)) * 0.05) * 0.25
				enemy_y(cnt) += cos(double(enemy_timer(cnt)) * 0.05) * 0.25
				if(enemy_x(cnt) < 32.0			) : enemy_x(cnt) += 0.5
				if(enemy_x(cnt) > 640.0 - 32.0	) : enemy_x(cnt) -= 0.5
				if(enemy_y(cnt) < 32.0			) : enemy_y(cnt) += 0.5
				if(enemy_y(cnt) > 480.0 - 32.0	) : enemy_y(cnt) -= 0.5
			}
			
			if(enemy_timer(cnt) \ 30 == 0){
				// 30での剰余が0:※弾のロジック
				enemy_flag(cnt)++
				if(enemy_flag(cnt) \ 8 == 0){ // 4way
					// flagを8で剰余したら0:4way弾発射
					up_cnt = cnt
					repeat 1 + level / 5
						// レベルに応じた回数発射
						v = double(cnt) * 0.7 + 0.8 // ※げ鷽瑤多いほど速い弾
						if(level < 20) : v = double(cnt) * 0.7 + 2.8 - 0.1 * double(level)
						// ※
						addenemy enemy_x(up_cnt) , enemy_y(up_cnt) ,  v * cos(rr + MATH_PI * 0.40) , v * sin(rr + MATH_PI * 0.40) , 0.0 ,ENEMY_TYPE_SHOT1
						addenemy enemy_x(up_cnt) , enemy_y(up_cnt) ,  v * cos(rr + MATH_PI * 0.15) , v * sin(rr + MATH_PI * 0.15) , 0.0 ,ENEMY_TYPE_SHOT1
						addenemy enemy_x(up_cnt) , enemy_y(up_cnt) ,  v * cos(rr - MATH_PI * 0.15) , v * sin(rr - MATH_PI * 0.15) , 0.0 ,ENEMY_TYPE_SHOT1
						addenemy enemy_x(up_cnt) , enemy_y(up_cnt) ,  v * cos(rr - MATH_PI * 0.40) , v * sin(rr - MATH_PI * 0.40) , 0.0 ,ENEMY_TYPE_SHOT1
					loop
					ds_play SND_ENEMYSHOT
				}
				if(enemy_flag(cnt) \ 8 == 4){	// にんじんショット
					// flagを8で剰余したら4:にんじんショット
					sp = 6.0 + double(level) * 0.1
					// 自機に向けて発射
					addenemy enemy_x(cnt) , enemy_y(cnt) , sp * cos(rr) , sp * sin(rr) , rr ,ENEMY_TYPE_SHOT2
					up_cnt = cnt
					repeat level / 7
						// ※ァ5°」の開きを持つ2way弾。回数が多いほど幅が広がる
						r = MATH_PI / 36.0 * double(cnt + 1)
						sp *= 0.8
						addenemy enemy_x(up_cnt) , enemy_y(up_cnt) ,  sp * cos(rr + r) , sp * sin(rr + r) , 0.0 ,ENEMY_TYPE_SHOT1
						addenemy enemy_x(up_cnt) , enemy_y(up_cnt) ,  sp * cos(rr - r) , sp * sin(rr - r) , 0.0 ,ENEMY_TYPE_SHOT1
					loop
					ds_play SND_ENEMYSHOT
				}
			}
		}

 

牛乳自体の動きは、なすとほぼ同じなので解説を省略します。

雰囲気としては、なすよりも少しキレがある感じですね(´∀`)

 

ただ、※,里箸海蹐魯譽戰襯妊競ぅ鹽に重要な部分です。

	enemy_flag(cnt) = rnd(8) // 「0」が4way「4」がにんじんなので、1秒(30FPS)の間に25%の確率で発射

 

※△砲△襪茲Δ法牛乳はtimerが「30」での剰余が0の場合に弾を発射します。

	if(enemy_timer(cnt) \ 30 == 0){

そして、えぐぜりにゃ〜のフレームレートは「30FPS」で、ちょうど1秒間になりますね。

また、乱数は0〜7の値を取り、「0」または「4」のとき弾を撃つので、2÷8=0.25となります。

 

つまり1秒間に25%の確率で弾を発射するわけです。

 

 

ではでは、弾の発射部分を見ていきます。

まずは4way弾です。

※で「addenemy」関数を呼び出し、弾を生成しているわけですが、

3つ目と4つ目の引数に注目です。

v * cos(rr + MATH_PI * 0.40) , v * sin(rr + MATH_PI * 0.40)
v * cos(rr + MATH_PI * 0.15) , v * sin(rr + MATH_PI * 0.15)
v * cos(rr - MATH_PI * 0.15) , v * sin(rr - MATH_PI * 0.15)
v * cos(rr - MATH_PI * 0.40) , v * sin(rr - MATH_PI * 0.40)

これは何をしているのかというと、、、。

rr」は自機がいる方向です。

それを軸に、

「MATH_PI * 0.40」「MATH_PI * 0.15」「-MATH_PI * 0.15」「-MATH_PI * 0.40」

それぞれの方向に弾を発射しています。

MATH_PIは「3.14」なので、180°です。

つまり、

MATH_PI * 0.40=180°*0.40=72°

MATH_PI * 0.15=180°*0.15=27°

-MATH_PI * 0.15=180°*0.15=-27°

-MATH_PI * 0.40=180°*0.40=-72°

という方向に発射しているわけです。

 

イメージとしてはこんな感じです。

f:id:kenmo:20060224185338p:image

 

あと、レベルが上がるほど、発射回数を増やしています。(※ぁ

そして、多くなるほど速度が速いです。

 

これはどういうものかというと、、、。

唐突ですが、具体的には、

自転車」と「原チャリ」と「車」が同時にスタートする様子を浮かべてください。

 

時間がたつにつれ、それぞれの差は「ぐいーん」と広がりますよね。

 

そういった弾幕を発射しているのが、このロジックになります。

(弾幕シューでは定番ともいえるロジックなので、覚えておいてソンはないと思います)

 

 

そして、にんじんショットの部分です。

にんじんそのものは問題ないと思います。

その後の2way弾ですが、(※ァ

MATH_PI / 36.0=180°÷36.0=5°

ということで、5°の開きの2way弾を発射していることになります。

 

 

プリン

プリンは、、、牛乳と似た部分が多く、牛乳が理解できていれば、そんなに難しくないので省略です(´∀`;

一応、コメントを追加しておきましたので、参考にしてもらえるとありがたいです。

		if(enemy_type(cnt) == ENEMY_TYPE_MID2){
			enemy_timer(cnt)++
			if(enemy_timer(cnt) == 1) : enemy_flag(cnt) = rnd(8)
			if(enemy_timer(cnt) \ 120 == 0){
				// 120での剰余が0:テキトーに16方向への移動
				sp = 4.0 + double(level) * 0.2
				rr = double(rnd(16)) / 8.0 * MATH_PI
				enemy_sx(cnt) = sp * cos(rr)
				enemy_sy(cnt) = sp * sin(rr)
			}else{
				// 画面外にでないように移動量を反転・直線移動
				if(enemy_x(cnt) < 32.0			& enemy_sx(cnt) < 0) : enemy_sx(cnt) *= -1.0
				if(enemy_x(cnt) > 640.0 - 32.0	& enemy_sx(cnt) > 0) : enemy_sx(cnt) *= -1.0
				if(enemy_y(cnt) < 32.0			& enemy_sy(cnt) < 0) : enemy_sy(cnt) *= -1.0
				if(enemy_y(cnt) > 480.0 - 32.0	& enemy_sy(cnt) > 0) : enemy_sy(cnt) *= -1.0
				enemy_x(cnt) += enemy_sx(cnt)
				enemy_y(cnt) += enemy_sy(cnt)
				enemy_sx(cnt) *= 0.95
				enemy_sy(cnt) *= 0.95
			}
			
			if(enemy_timer(cnt) \ 30 == 0){
				enemy_flag(cnt)++
				if(enemy_flag(cnt) \ 8 == 0){ // 8ばら撒き
					vx = enemy_x(cnt)
					vy = enemy_y(cnt)
					repeat 1 + level / 5
						// レベルが高いほどたくさん撃つ
						sp = double(cnt) * 0.6 + 0.8 // たくさん撃つほど速い弾
						if(level < 20) : sp = double(cnt) * 0.6 + 2.8 - 0.1 * double(level)
						ty = ENEMY_TYPE_SHOT1
						if(cnt == level / 5) : ty = ENEMY_TYPE_SHOT2
						repeat 8
							// 全方向8way
							rr = MATH_PI / 4.0 * double(cnt)
							addenemy vx , vy ,  sp * cos(rr) , sp * sin(rr) , rr , ty
						loop
					loop
					ds_play SND_ENEMYSHOT
				}
			}
			if(enemy_timer(cnt) \ 3 == 0 & enemy_timer(cnt) \ 60 < level / 3 + 1){
				if(enemy_flag(cnt) \ 8 == 4){	// 大根みそー
					sp = 8.0 + double(level) * 0.1
					// 32方向のいずれかにランダム発射
					rr = double(rnd(32)) / 16.0 * MATH_PI
					addenemy enemy_x(cnt) , enemy_y(cnt) , sp * cos(rr) , sp * sin(rr) , rr ,ENEMY_TYPE_SHOT4
					ds_play SND_ENEMYSHOT
				}
			}
		}

 

当たり判定

最後は当たり判定ですが、これも円の当たり判定で、

特にこれといった処理をしていないので、省略です。

 

 

 

今日はここまでですー。

次回は敵の生成ロジックを解説する予定です。(たぶん