いろんな飛び道具を使いたいな


 えー、今回はかなりハイテクニックな香りのする内容となっています。 飛び道具を複数種類もっていて、なおかつ、当たった時の動作が違ったものにするには、 どうすればいいかということを解説します。

 具体例を挙げてしまえば「やっこちゃんの飛び道具はどうやって実現しているか?」 「当たると転ぶ飛び道具と転ばない飛び道具の二つを持ちたい」ということをとりあげます。

 まず、気をつけなければいけないのは「飛び道具」「波動くらい」「空中波動くらい」 は動作IDが決められているということです。これはつまり「波動拳くらい」 「ファイヤー波動拳くらい」のような関数を用意して それぞれを適当なIDに割り当てることは出来ないことを意味しています。

 IDが変えられないということは動作登録()で登録した関数のなかで、 処理を振り分けなければいけません。

void 飛び道具(int p1,int p2,short x1,short x2,short num,short pat)
{
  switch(飛び道具種類) {
  case 0:
    爆炎龍(p1,p2,x1,x2,num,pat);
    break;
  case 1:
    手裏剣(p1,p2,x1,x2,num,pat);
    break;
  case 2:
    空中手裏剣(p1,p2,x1,x2,num,pat);
    break;
  }
}

 飛び道具の場合、画面内に二つ以上の飛び道具が存在出来ないため、 このように単純な方法で実現可能です。 「飛び道具種類」という変数に必殺技などの動作関数で種類に合わせた値を代入することで、 動作の違う複数種類の飛び道具を扱うことが出来ます。 なお、「飛び道具種類」という変数はプログラム全体から見える変数にしておいた方が便利でしょう。

 飛び道具の動作関数は呼び出し方がどれも同じになることが多いので、 「関数へのポインタ」を使うことでプログラムが簡略化出来、 コンパイラの最適化にも期待出来ます。

 関数も変数と同様に、メモリ上のとあるアドレスに割り当てられます。 つまり、関数名も唯一のアドレスを指すものとなります。 関数名がアドレスであるならばそれを指し示すポインタをつくることが出来ます。 これが「関数へのポインタ」です。関数へのポインタの定義は
float (*fp)(float)=sin;
のような形で行なわれます。この場合戻り値がfloatで、float型の引き数をひとつ持つ sin()という関数を指し示すポインタ変数を定義しています。 関数へのポインタの場合、アドレスが前後にずれるとまったく意味のない数字になるため、 一度値を代入したら、以後内容を変更することはほとんどありません。

 関数へのポインタはその性質上、配列と組み合わせて用いられることが多いです。
float *fp[](float)={sin,cos,tan,atan};
 この場合、配列の添字によって実行する関数を変えることが出来ます。fp[0]()の時sin()関数を、 fp[1]()の時はcos()関数を呼び出すことが出来ます。

 飛び道具関数に関数へのポインタ配列を用いた場合は
void 飛び道具(int p1,int p2,short x1,short x2,short num,short pat)

static void *fp[](int,int,short,short,short,short)={
  爆炎龍,手裏剣,空中手裏剣
};


  fp[飛び道具種類](p1,p2,x1,x2,num,pat);

こうなります。


 「波動くらい」の動作関数も同じ方法を使おう、といいたいところですがそうはいきません。 では、同様なプログラムではどんな不都合があるのでしょうか?
void 波動くらい(int p1,int p2)
{
static void *fp[](int,int)={
  波動A,波動B,波動C,波動D
};


  fp[波動くらい種類](p1,p2);


 波動くらいの種類を決定するのは「波動くらい種類」というフラグ変数です。 この変数の更新タイミングを考えてみましょう。

 単純に考えると、飛び道具の放出と同時に更新するのがよさそうです。 しかし、この時、相手が別の「波動くらい」状態だった場合はまずいことになります。  当たると転ぶ飛び道具をA、当たっても転ばない飛び道具をBとします。 Bの飛び道具を当てて相手がのけぞっている時、Aを出すと波動くらいの処理が途中から A用のものに移ってしまいます。
 のけぞっている途中からいきなり転ぶ、くらいならまだいいのですが、 複雑な動作をさせている場合では、甚大な被害を及ぼすことも考えられます。


 波動くらい動作の最中でフラグ変数を変更してはならないことはわかったと思います。 では、どのタイミングで変更すればいいのか。それは飛び道具が当たった瞬間です。

 というわけで飛び道具が当たった瞬間を知る方法を探してみるわけですが、 いくつか思い付くと思います。 OPT_サイクルで監視、飛び道具動作関数で監視、OPT_衝撃硬直で監視・・・。

 出来ないことはないと思いますが、非常に面倒くさそうです。 私が思い付いた中で一番簡単な方法は、「波動くらい」動作関数の最初のサイクルで更新する、 というものです。ここなら、飛び道具が当たったら必ず呼ばれますからね。

 それなら、ってことで各副プログラムの先頭で
void 波動A(int p1,int p2)
{
short  c=カウンタ初期設定2(p1,100);
  if(c>=100) {
    波動くらい種類=飛び道具種類;
    ...
    ...
}
と書いても駄目です。まず、カウンタの初期値は関数ごとに違っている可能性があります。 初期値の問題は統一することで解消できますが、別の問題点もあります。

 波動くらいの最初のサイクルでは何も出来なくなります。 Aでは最初のサイクルから身体無敵化()をしたい、でもBでは無敵になっちゃ困る、 と思ってもどうしようもありません。
 いい加減、もったいぶらずにうまく解決する方法を書きます。
void  波動くらい(int p1,int p2)
{
static void (*fp[])(int,int,short)={
  波動A,波動B,波動C,波動D
};
static char くらい種類;
short c=カウンタ初期設定2(p1,1000);

  if(c>=1000) {
    くらい種類=波動くらい種類;
  }
  (fp[くらい種類])(p1,p2,c);
}

void  波動A(int p1,int p2,short c)
{
  if(c>=1000) {
    カウンタ設定(p1,40+1);
    表示セル設定(CELL_転ぶ);
    COMBO増加(p1);
    COMBO表示(p1);
    FAチェック(p1,p2);
    身体無敵化(p1);
  }
  else if(c>=40) {
     ....
     ....
}

void  波動B(int p1,int p2,short c)
{
  if(c>=1000) {
    カウンタ設定(p1,30+1);
    表示セル設定(CELL_地上くらい0);
    COMBO増加(p1);
    FAチェック(p1,p2);
  }
  else if(c>=30) {
     ....
     ....
}

  ....
  ....


 「波動くらい種類」というフラグ変数は飛び道具放出と同時に設定します。 「波動くらい」関数のカウンタ初期設定2()で1000を設定していますが、 これは副関数で必要とするカウンタの最大値以上ならいくつでも構いません。

 これで、「波動くらい関数の最初のサイクルでフラグ変数をセット」 「波動くらいの最初のサイクルから個別の処理を行なう」ことが出来ます。

 実際、やっこちゃんはこの方法を使っていて、何ら問題は起きていませんから実績も十分です。 まあ、あくまで一例ですからほかにもっといい方法があるかもしれません。 アルゴリズムなんていうのはそういうもんですから。

 そろそろ、本気でネタがないです。だれかネタをください。 私の代わりに書いてくれる人でも可です。


ひとつ前にもどるトップページにもどる