第19回 波符「赤眼催眠マインドシェイカー」 Easy (1)

今回から、変わった動作を行う弾を作ってみましょう。そこでは、マイクロスレッドが大いに活躍します。

要旨

それではいってみましょう。

ブール値

さて、今回からは別のスペルカードを作ってみたいと思います。特殊な動きを行う弾幕の例として、東方永夜抄の波符「赤眼催眠マインドシェイカー」 Easy を作ってみましょう。

先ずは、このスペルがどのようなものなのかを視てみましょう。このスペルは、次のような流れになります。

  1. 速度 1 の円状 48-way の赤弾を 3.75 度ずつずらして2つ放つ。
  2. 50 フレーム待つ。
  3. 幻視(100 フレーム)によりそれぞれが逆方向に斜めにずれる。
  4. 速度 1.25 の円状 24-way の赤弾を 3.75 度ずつずらして2つ放つ。
  5. 速度 0.95 の円状 24-way の赤弾を、4. から 7.5 度ずらしたところから 3.75 度ずつずらして2つ放つ。
  6. 25 フレーム待つ。
  7. 速度 1.05 の円状 24-way の青弾を 3.75 度ずつずらして2つ放つ。
  8. 速度 0.85 の円状 24-way の青弾を、7. から 7.5 度ずらしたところから 3.75 度ずつずらして2つ放つ。
  9. 25 フレーム待つ。
  10. 1. に戻る。

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 に指定している truefalse というのは一体何なんでしょうか?

これらを日本語に訳すと、true は「真」、false は「偽」になります。そうです。第7回で話した、あの「真」と「偽」です。truefalse はそれぞれ「真」と「偽」を値にしたものであり、真偽値(ブール値)と呼ばれています。例えば、

if(true) { ... }

とすると、必ず { ... } が実行されます。実際にはこのように直接 if に指定するのではなく、何らかの変数に入れて

if(right) { ... }

のようにして使います。

ここでは、幻視で右にブレる場合には righttrue を、左にブレる場合には false を渡すようにしています。ただ、実際に right を使うのは、次回にしたいと思います。

さて、これらを使って、とりあえず弾を撃つだけのプログラムを作ってみましょう。ここにそのプログラムを置いておきます。基本的な部分は、前のとほとんど同じですね。幻視の部分は、とりあえずウェイトをかけるだけにしておきました。

配列

それでは、shot1shot2 をループ化してみましょう。この程度であればループにしなくても良さそうですが、練習がてらループにしてみます。

先ずは shot1 です。2つの処理で違うのは、dirright の部分だけです。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 ずつ減っていき、最後のループでは <先端値> になります。これは、配列を逆順で扱うときに便利です。

要旨

次回は、幻視を実装します。このような特殊な弾を作るには、前述の「オブジェクト弾」が必要になります。

第18回 | 第20回 | 目次へ戻る

この講座はロベールが作成しました。
inserted by FC2 system