今回から、変わった動作を行う弾を作ってみましょう。そこでは、マイクロスレッドが大いに活躍します。
true
と false
はそれぞれ「真」と「偽」を値化したもの。ascent
と descent
を使えば、配列の添え字用の変数を変化させつつループをする部分をまとめて書けます。それではいってみましょう。
さて、今回からは別のスペルカードを作ってみたいと思います。特殊な動きを行う弾幕の例として、東方永夜抄の波符「
先ずは、このスペルがどのようなものなのかを視てみましょう。このスペルは、次のような流れになります。
way 弾の発射方向は、ランダムに決定されます。あと、500 フレーム後に守備力が大幅に落ちます。
この弾幕を作るにあたっては、円状 n-way 弾を撃つ関数が必要になります。その弾は幻視でぶれる弾ではあるのですが、最初は普通の弾にしておきましょう。
// 円状 n-way 弾 // way : way 数 // v : 速度 // dir : 基準とする方向 // type : 弾のタイプ // right : 幻視で右に移動するかどうか function nwayCircle(way, v, dir, type, right) { let span = 360 / way; let angle = dir; loop(way) { CreateShot01(GetX, GetY, v, angle, type, 0); angle += span; } }
ほとんど問題はないと思いますが、right
という、使われていない謎の引数があります。これは後で使うつもりですが、幻視で右に移動するか、左に移動するかを表すものです。
では、これを使って、48-way 弾を撃つ関数と、24-way 弾を撃つ関数を作ってみましょう。
// 1重の円状 n-way 弾 // v : 速度 // type : 弾のタイプ function shot1(v, type) { let way = 48; let dir = rand(-180, 180); nwayCircle(way, v, dir , type, true ); nwayCircle(way, v, dir + 3.75, type, false); } // 2重の円状 n-way 弾 // v1 : 片方の速度 // v2 : もう一方の速度 // type : 弾のタイプ function shot2(v1, v2, type) { let way = 24; let dir = rand(-180, 180); nwayCircle(way, v1, dir , type, true ); nwayCircle(way, v1, dir + 3.75, type, false); nwayCircle(way, v2, dir + 7.50, type, true ); nwayCircle(way, v2, dir + 11.25, type, false); }
ループを使わずに、愚直に処理を並べてるだけです。ただ、nwayCircle
の最後の引数 right
に指定している true
と false
というのは一体何なんでしょうか?
これらを日本語に訳すと、true は「真」、false は「偽」になります。そうです。第7回で話した、あの「真」と「偽」です。true
と false
はそれぞれ「真」と「偽」を値にしたものであり、真偽値(ブール値)と呼ばれています。例えば、
if(true) { }
とすると、必ず {
が実行されます。実際にはこのように直接 }if
に指定するのではなく、何らかの変数に入れて
if(right) { }
のようにして使います。
ここでは、幻視で右にブレる場合には right
に true
を、左にブレる場合には false
を渡すようにしています。ただ、実際に right
を使うのは、次回にしたいと思います。
さて、これらを使って、とりあえず弾を撃つだけのプログラムを作ってみましょう。ここにそのプログラムを置いておきます。基本的な部分は、前のとほとんど同じですね。幻視の部分は、とりあえずウェイトをかけるだけにしておきました。
それでは、shot1
と shot2
をループ化してみましょう。この程度であればループにしなくても良さそうですが、練習がてらループにしてみます。
先ずは shot1
です。2つの処理で違うのは、dir
と right
の部分だけです。dir
の部分は 3.75 ずつ増やせばいいだけですが、right
はどのようにすればループを使って表現できるでしょうか? これは、毎回真偽がひっくり返ると考えれば良いでしょう。実際、shot2
では真偽が交互に現れていますね。
真と偽をひっくり返すには、!
という演算子を使います。例えば、
! true
とすれば、これは偽になります。これを踏まえると、変数 right
の中に入ってる真偽値をひっくり返すには、
right = ! right;
とすれば良いことが分かります。
以上より、shot1
は次のように書き換えることができます。
function shot1(v, type) { let dir = rand(-180, 180); let right = true; loop(2) { nwayCircle(48, v, dir, type, right); dir += 3.75; right = ! right; } }
次は shot2
です。変更箇所はほとんど shot1
と同じなのですが、問題は速度です。今のままだと、
function shot2(v1, v2, type) { let way = 24; let dir = rand(-180, 180); let right = true; loop(2) { nwayCircle(way, v1, dir , type, right); nwayCircle(way, v2, dir + 7.50, type, right); dir += 3.75; right = ! right; } }
というところまではいけるのですが、この2つの nwayCircle
をさらにまとめるには、速度の部分をまとめる必要があります。
方法はいくつかありますが、これには配列というものを使うのが一番分かりやすいやり方でしょう。配列というのは、値をいくつか並べた組のことです。各値にはそれぞれ通し番号が振られていて、配列から値を取り出すには、その通し番号を使います。配列に含まれる各値のことを、要素と言います。
配列は、例えば次のような形で書きます。
array = [3, 14, 15, 92, 65];
ここでは、array
という変数に、配列 [3, 14, 15, 92, 65]
を代入しています。配列を作るには、いくつかの値をコンマで区切って並べ、それを [ ]
で囲みます。
そして、例えば、配列の最初の要素を使いたい場合は
array[0]
のようにします。配列、もしくは配列の入った変数の後ろに [ ]
を書き、その中に使いたい要素の通し番号を書きます。この通し番号は 0 から始まることに注意して下さい。なので、2番目の要素を使いたい場合は
array[1]
と、通し番号には 1 を使うことになるわけです。
さて、これだけでは、上の変数 array
を使う代わりに、
array0 = 3; array1 = 14; array2 = 15; array3 = 92; array4 = 65;
としたのでも、何も変わらないように思えます。array[0]
の代わりに array0
を、array[1]
の代わりに array1
を使ったのではダメなのでしょうか?
実は、大きな違いがあるのです。配列では、通し番号に変数が使えるのです。配列を使ってない方では、i = 2;
として arrayi
と書いても、それは array2
にはならず、 arrayi
という名前の別の変数を表すだけになります。ところが配列の場合は、array[i]
とすれば array[2]
のことになるのです。
これを踏まえて、shot2
を書き換えてみましょう。
// 2重の円状 n-way 弾 // v : 速度(要素数2の配列) // type : 弾のタイプ function shot2(v, type) { let dir = rand(-180, 180); let right = true; let i = 0; loop(2) { loop(2) { nwayCircle(24, v[i], dir, type, right); dir += 3.75; right = ! right; } i++; } }
引数から渡される速度を配列にしておけば、このようにループで速度を切り替えることができるのです。
以上の変更を加えたプログラムをここに置いておきます。あとは幻視を実装すれば終わりなのですが、それについては次回に回したいと思います。
配列を扱う際には、配列の添え字(要素番号)を変えつつループする、ということがよくあります。実際、上の例でも i
という添え字用の変数を用意しました。そこで、このようなループをもっと手軽に書く方法が用意されています。例えば、上で書いた shot2
は次のようになります。
// 2重の円状 n-way 弾 // v : 速度(要素数2の配列) // type : 弾のタイプ function shot2(v, type) { let dir = rand(-180, 180); let right = true; ascent(i in 0..2) { loop(2) { nwayCircle(24, v[i], dir, type, right); dir += 3.75; right = ! right; } } }
比較してみると、
let i = 0;
loop(2)
i++;
の部分が
ascent(i in 0..2)
に置き換わってることが分かります。誤解を恐れずに言うなら、これは
ascent( in 0.. )
となっており、 の値は
0
から始まり、ループする毎に 1 ずつ増えていき、最後のループでは 1
になります。実用上はこの認識で構わないと思います。
-
より正確に言うなら、
ascent(let in .. ) loop
となっています。 の値は
から始まり、ループする毎に 1 ずつ増えていき、最後のループでは
1
になります。 - を含まないのは、上で書いた
ascent( in 0.. )
とできるようにするためです。
逆に、値を減らしていくタイプの命令もあり、
descent(let in .. ) loop
となっています。 の値は
1
から始まり、ループする毎に 1 ずつ減っていき、最後のループでは - になります。これは、配列を逆順で扱うときに便利です。
true
と false
はそれぞれ「真」と「偽」を値化したもの。ascent
と descent
を使えば、配列の添え字用の変数を変化させつつループをする部分をまとめて書けます。次回は、幻視を実装します。このような特殊な弾を作るには、前述の「オブジェクト弾」が必要になります。