えぐぜりにゃ〜ソース解説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 }
大体このような動きをします。
この一連の動作を切り替えるために、
ポッキー&大根では、タイマーを使っています。
「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) } }
タイマ | 行動 |
---|---|
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) } }
動きとしては、このようになります。
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°
という方向に発射しているわけです。
イメージとしてはこんな感じです。
あと、レベルが上がるほど、発射回数を増やしています。(※④)
そして、多くなるほど速度が速いです。
これはどういうものかというと、、、。
唐突ですが、具体的には、
「自転車」と「原チャリ」と「車」が同時にスタートする様子を浮かべてください。
時間がたつにつれ、それぞれの差は「ぐいーん」と広がりますよね。
そういった弾幕を発射しているのが、このロジックになります。
(弾幕シューでは定番ともいえるロジックなので、覚えておいてソンはないと思います)
そして、にんじんショットの部分です。
にんじんそのものは問題ないと思います。
その後の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 } } }
当たり判定
最後は当たり判定ですが、これも円の当たり判定で、
特にこれといった処理をしていないので、省略です。
今日はここまでですー。
次回は敵の生成ロジックを解説する予定です。(たぶん