技を追加してみよう(その2)


 前回は技を追加する時の準備段階について説明しました。 今回は実際の技のプログラムを解説していきます。

 前回の説明でいくつかソースを書き換えていましたが、その部分を再度載せておきます。
ACTION.H
#define  ACT_乱舞	20			/*適当な位置に追加*/


#define  CELL_前後ジャンプ6	248
#define  CELL_超必殺00		256		/*この行から追加*/
#define  CELL_超必殺01		257
#define  CELL_超必殺02		258
#define  CELL_超必殺03		259
#define  CELL_超必殺04		260
#define  CELL_超必殺05		261
#define  CELL_超必殺06		262
#define  CELL_超必殺07		263
#define  CELL_超必殺08		264
#define  CELL_超必殺09		265
#define  CELL_超必殺10		266
#define  CELL_超必殺11		267
#define  CELL_超必殺12		268
#define  CELL_超必殺13		269
#define  CELL_超必殺14		270
#define  CELL_超必殺15		271
#define  CELL_超必殺16		272
#define  CELL_超必殺17		273
#define  CELL_超必殺18		274
#define  CELL_超必殺19		275
#define  CELL_超必殺20		276		/*ここまで追加*/


/*..........  キャラ固有動作関数のプロトタイプ宣言  ..........*/

void  乱舞(int,int);			/*プロトタイプ宣言の最後の行に追加*/


/*..........  キャラ固有コマンド判定関数のプロトタイプ宣言  ..........*/

void  乱舞コマンド(int);		/*上と同様に最後に追加*/
ACTION.C
void main( int argc, char *argv[] )

  セル登録( CELL_前後ジャンプ6	, &cell_前後J6		);
  セル登録( CELL_超必殺00	, &cell_垂直J0		);	/*ここから追加*/
  セル登録( CELL_超必殺01	, &cell_アッパー0	);
  セル登録( CELL_超必殺02	, &cell_アッパー1	);
  セル登録( CELL_超必殺03	, &cell_アッパー2	);
  セル登録( CELL_超必殺04	, &cell_キック0		);
  セル登録( CELL_超必殺05	, &cell_キック1		);
  セル登録( CELL_超必殺06	, &cell_回転足払い0	);
  セル登録( CELL_超必殺07	, &cell_回転足払い1	);
  セル登録( CELL_超必殺08	, &cell_回転足払い2	);
  セル登録( CELL_超必殺09	, &cell_回転足払い3	);
  セル登録( CELL_超必殺10	, &cell_回転足払い4	);
  セル登録( CELL_超必殺11	, &cell_旋風脚3		);
  セル登録( CELL_超必殺12	, &cell_旋風脚2		);
  セル登録( CELL_超必殺13	, &cell_旋風脚1		);
  セル登録( CELL_超必殺14	, &cell_旋風脚0		);
  セル登録( CELL_超必殺15	, &cell_昇龍拳0		);
  セル登録( CELL_超必殺16	, &cell_昇龍拳1		);
  セル登録( CELL_超必殺17	, &cell_昇龍拳2		);
  セル登録( CELL_超必殺18	, &cell_昇龍拳3		);
  セル登録( CELL_超必殺19	, &cell_昇龍拳4		);
  セル登録( CELL_超必殺20	, &cell_昇龍拳5		);	/*ここまで*/
  
  /*..........  完了  ..........*/

void  初期化( int p1 , int p2 )

  動作登録( ACT_乱舞		, 乱舞			);
  /*動作登録関数登録部分のどこかに追加する*/

 以上です。

 では、新規追加技のプログラムリストを載せます。今回はこれについて解説していきます。
 このリストを「ACTION.C」の最後に追加し、上記の部分を修正してコンパイルすれば 新しい技が使えるようになります。
 体力が1/4以下の時に63216+ボタンで出すことが出来ます。 技の特徴としては、ストリートファイターZEROに見られるような、 とにかく技を出しながら突進するタイプです。 キャラによっては最後の一発が当たらないこともあります(調整不足)。

 ではソースについての解説です。

     1: /*---------------------------------------------------------------------------*/
     2: void	乱舞(int p1,int p2)
     3: /*---------------------------------------------------------------------------*/
     4: {
     5: static HIT_INFO	hit_乱舞1={3,1,0,0,6,中,PCM_HIT大,ACT_微衝撃};
     6: static HIT_INFO	hit_乱舞2={10,2,0,0,20,中,PCM_HIT大,ACT_微衝撃};
     7: static HIT_INFO	hit_乱舞3={4,2,0,0,20,中,PCM_HIT大,ACT_空中連続衝撃};
     8: static HIT_INFO	hit_乱舞4={6,2,0,255,20,中,PCM_HIT大,ACT_ふっとぶ};
 5〜8行はヒット情報です。説明の都合によりこの位置にあります。 普通はHIT_INFO.Hに書くのが妥当でしょう。
 ヒット情報について簡単に解説しておきます。ヒット情報のメンバは、先頭から順に
威力
削りダメージ
気絶ダメージ
燃焼時間
ヒットストップ
ガード位置
ヒット音
ヒット動作
相手に与えるダメージの基準値です。条件によって上下します。
ガードされた時に与えるダメージです。
相手に与える気絶ダメージです。
この値が1以上の時、相手に技が当たると数値サイクル分燃焼します。
技が当たった時の停止時間です。
下=しゃがみ防御、中=立ちorしゃがみ防御、上=立ち防御
ヒットした時に鳴らすPCMのID
ヒット後の相手の動作IDを指定します。


     9: static short	dy;
 9行目で静的変数を定義しています。これは技の最後の落下時に使います。 詳しくは後述。


    10: short	c=カウンタ初期設定(1000);
 10行目で、カウンタの新規設定&減少を行ないます。 カウンタ初期設定()の引き数が1000になっているのでカウンタは1000からスタートします。 なぜ、こんな大きい数字なのかというと、動作サイクル数は完成してみないとわからないからです。
 とりあえず、大きな数字を指定しておき、完成した時にカウンタを最適化します。 その方が楽です。


    11: 	残像表示();
    12: 	砂煙表示(p1,OFF);
    13: 	if(c>=901) 前方微移動(p1,p2,0,2);
 11,12行目はカウンタの値にかかわらず、毎サイクルで実行する処理なので、こ こに書かれています。
 13行はカウンタが901以上の時に実行されます。 技の実行中は常に移動させたいが900サイクルからは着地硬直なので、 移動するのは好ましくありません。よって、このように記述しています。

 14行からが各サイクルごとの処理になります。


    14: 	if(c>=1000) {
    15: 		身体実体化(p1);
    16: 		最多ヒット数設定(1);
    17: 		表示セル設定(CELL_超必殺00);
    18: 		カウンタ設定(p1,990+1);
    19: 	}
 14〜19行はカウンタが1000の時、つまり技の最初のサイクルで行なう処理になります。 技の最初のサイクルで行なうべき処理は、最多ヒット数設定()です。
 SFXVIでは、相手が現在攻撃判定をもっているか、攻撃を空振りしたか、攻撃判定をもつ前か、 といったことを知ることはきわめて難しく、最多ヒット数の値をかわりに判定に使うことで誤魔化しています。

最多ヒット数=0    技の空振り=隙
最多ヒット数≧1    攻撃判定あり=当て身可

 慣例的に上のように使われています。一般的に攻撃動作は、前動作→攻撃→後動作の三段階に分かれます。 この中で、空振りと認識させたい部分は後動作です。 また、実際には前動作にも攻撃判定がないのですが、ここで最多ヒット数を0にしてまうと、 CPUが技の隙と判断して突っ込んできてしまう可能性があります。
 よって最初のサイクルで最多ヒット数を設定することが望ましいでしょう。


 地上(空中)必殺技コマンド実行で呼び出された技は技の最初のサイクルから無敵になっています。 この無敵状態は自分で解除しない限り、その技が完了するまで継続します。 よって任意のサイクルで解除する必要があります。 この技では無敵時間は必要ないので最初のサイクルで身体実体化()を実行しています。

 ここでカウンタ設定(p1,990+1)を実行しています。 これは当初、カウンタが999から985の間は移動を行なうサイクルにしていたのですが、 長すぎたのでカウンタを進め、移動サイクルを短くしたのです。 あまり美しくないのですが、これだけ長いサイクルの動作を修正するのは少々骨なので。


    20: 	else if(c>=985) {
    21: 		前方移動(p1,p2,2);
    22: 		表示セル設定(CELL_超必殺00);
    23: 	}
 20〜23行は移動を行なうサイクルです。18行でカウンタをいじっているので、 カウンタが990〜985の間、この処理が行なわれます。


    24: 	else if(c>=984) {
    25: 		表示セル設定(CELL_超必殺01);
    26: 		ボイス再生(p1,0,PCM_振り2);
    27: 	}
 24〜27行では表示するセルも替わり、PCMを再生しています。この処理はカウ ンタが984の時のみ行なわれます。


    28: 	else if(c>=982) {
    29: 		表示セル設定(CELL_超必殺01);
    30: 	}
 28〜30行。セルを表示しているだけです。


    31: 	else if(c>=980) {
    32: 		表示セル設定(CELL_超必殺02);
    33: 		攻撃ポイント設定(&hit_乱舞1,OFF);
    34: 	}
 31〜34行。カウンタが981,980の時攻撃判定を持たせます。 33行から、この時のヒット情報は"hit_乱舞1"で、必殺技でのキャンセルは不可になっています。
 カウンタが981の時に、相手に技が当た(ガード・ヒットどちらでも)れば、 最多ヒット数は−1され0になり、980の時に攻撃判定は現れません。

 また、重要なことですが、この技では技が発動してから11サイクル目に初めて攻撃判定が発生します。 このサイクル数によって連続技が成立したりしなかったりするわけです。

 デフォルトくらい動作の場合、連続技の成立許容サイクルは (括弧内は与えるダメージが5以下の時のものです)
地上くらい1
地上くらい2
波動くらい
微衝撃
19(14)
19(14)
21
20(14)
となっています。 システムの動作を完全に把握しているわけではないので1サイクル分前後にずれている可能性があります。 また、連続技が成立する最後のサイクルで攻撃を当てると、実際には連続技でありながら、連続技とみなされません。

 以上を踏まえて攻撃判定出現開始サイクルを決めましょう。 また、技の最初のサイクルに攻撃判定を持たせることはやめましょう。 至近距離で出されるとガードが出来ない技になってしまいます。


    35: 	else if(c>=977) {
    36: 		表示セル設定(CELL_超必殺03);
    37: 	}
    38: 	else if(c>=975) {
    39: 		表示セル設定(CELL_超必殺02);
    40: 	}
    41: 	else if(c>=973) {
    42: 		表示セル設定(CELL_超必殺01);
    43: 	}
 35〜43行はセルを表示するだけです。


    44: 	else if(c>=972) {
    45: 		表示セル設定(CELL_超必殺04);
    46: 		ボイス再生(p1,0,PCM_振り2);
    47: 		最多ヒット数設定(1);
    48: 	}
 44〜48行。カウンタの値が972の時のみ行なわれます。 ここで2発目の攻撃に備えて、PCMの再生と最多ヒット数の設定を行なっています。

 49〜101行はいままでと同じことを行なっているので省略します。

 102行からは乱舞の締めに昇龍拳を繰り出します。


   102: 	else if(c>=935) {
   103: 		表示セル設定(CELL_超必殺16);
   104: 		攻撃ポイント設定(&hit_乱舞2,OFF);
   105: 	}
 102〜105行。ヒット情報を変えています。それ以外はほかの部分と違いはありません。


   111: 	else if(c>=930) {
   112: 		縦方向移動(p1,-4);
   113: 		前方移動(p1,p2,4);
   114: 		表示セル設定(CELL_超必殺17);
   115: 	}
   116: 	else if(c>=926) {
   117: 		縦方向移動(p1,-5);
   118: 		前方移動(p1,p2,2);
   119: 		表示セル設定(CELL_超必殺17);
   120: 		攻撃ポイント設定(&hit_乱舞3,OFF);
   121: 	}
   122: 	else if(c>=920) {
   123: 		縦方向移動(p1,-6);
   124: 		前方移動(p1,p2,1);
   125: 		表示セル設定(CELL_超必殺17);
   126: 	}
   127: 	else if(c>=919) {
   128: 		縦方向移動(p1,-6);
   129: 		前方移動(p1,p2,1);
   130: 		表示セル設定(CELL_超必殺17);
   131: 		最多ヒット数設定(1);
   132: 	}
   133: 	else if(c>=910) {
   134: 		縦方向移動(p1,-4);
   135: 		表示セル設定(CELL_超必殺17);
   136: 		攻撃ポイント設定(&hit_乱舞4,OFF);
   137: 	}
   138: 	else if(c>=905) {
   139: 		縦方向移動(p1,-2);
   140: 		表示セル設定(CELL_超必殺17);
   141: 	}
 111〜141行。上昇および前方移動をしながら攻撃を行ないます。 移動力は実際に動かしながら吟味します。 上下動は重力の影響を考えて、ジャンプの頂上付近に近づくにつれて、移動力を落とすと自然な動きに見えます。


   142: 	else if(c>=904) {
   143: 		表示セル設定(CELL_超必殺18);
   144: 		最多ヒット数設定(0);
   145: 		dy=0;
   146: 	}
 142〜146行。上昇しきった瞬間です。このサイクルで最多ヒット数を強制的に0にしています。 このサイクル以降は当て身されないため、また技の隙であることをはっきりさせるためにも、 なるべく最多ヒット数は0にしましょう。
 また、等加速度運動を行なうために変数dyを0にしています。


   147: 	else if(c>=902) {
   148: 		表示セル設定(CELL_超必殺18);
   149: 	}
 147〜149行。ジャンプの頂点でやや静止させています。


   150: 	else if(c>=901) {
   151: 		if(身体Y位置(p1)<BASE_LINE) {
   152: 			dy++;
   153: 			縦方向移動(p1,dy>>1);
   154: 			カウンタ維持(p1);
   155: 		}
   156: 		else {
   157: 			身体Y位置設定(p1,BASE_LINE);
   158: 			ボイス再生(p1,0,PCM_着地);
   159: 		}
   160: 		表示セル設定(CELL_超必殺19);
   161: 	}
 150〜161行。ジャンプの下降から着地の瞬間までをここで行ないます。 身体Y位置が、BASE_LINEで示される値より小さければ、空中にいるということなのでその間は下降させます。 空中にいる間はカウンタを進めずに、この行で処理を続けたいのでカウンタ維持()を使っています。
 Y位置がBASE_LINEを越えたら、着地PCMを再生し、Y座標を念のためBASE_LINEにあわせます。


   162: 	else if(c>=895) {
   163: 		表示セル設定(CELL_超必殺20);
   164: 	}
 162〜164行。着地の隙を900〜895の6サイクルにしています。 この隙はかなり重要です。空中の相手を攻撃するより地上の相手を攻撃することのほうが得策である、 と考える人は多いでしょう。というよりシステム的にも地上の相手の方が攻撃を与えやすいですからね。  ですから、ほとんどの人は相手が高くジャンプして隙だらけで落下してきても、 着地まで待ってしまいます。それを考慮して隙をつくりましょう。


   165: 	else {
   166: 		攻撃動作完了(p1,p2);
   167: 	}
 165〜167行。攻撃動作を全て終えたので動作終了処理を行ないます。 この処理はカウンタの値が894以下の時に行なわれます。 攻撃動作完了()を呼び出した次のサイクルから、処理は別の動作にうつるので、 カウンタは893以下になることはありません。
 **完了()は現在の動作の種類によって使い分ける必要があります。 使い分けを間違えた場合の動作は保証されませんから注意しましょう。 デッドロックの原因にもつながります。

攻撃動作完了()
ジャンプ攻撃動作完了()
一般動作完了()
起き上がり完了()
不時着完了()
地上での攻撃動作の時に使う
空中必殺(基本)技コマンド実行()で実行される動作の時に使う
くらい動作などの時に使う
起き上がり動作の時に使う
ACT_不時着の時に使う

 また、ここではセルの表示を行なっていません。 本来ならここでもセルを表示しなければならないのですが、最後のサイクルなので省略しました。 表示セル設定を行なわなかったサイクルでは前のサイクルで表示していたセルを表示するようです。
 ただし、いつ不都合が起こるかわからないのでなるべくセルの表示は行ないましょう。


 続いてコマンド解析ルーチンの説明に入ります。

 SFXVIのコマンド解析方法ですが、これにはキューを使います。 レバー、トリガ独立して、過去の入力状態を記録するバッファが用意されています。 このバッファをキューと呼んでいます。 このバッファを新しいデータから順にチェックする関数が用意されています。
 そこでコマンド解析ルーチンは、通常記述するコマンドの逆からチェックしていくことになります。

 例えば波動拳コマンド(236+P)をチェックするには、まずボタンが押されたかどうかを調べ、 押されていたら6(=レバー前)が過去に入力されていたかどうかを調べます。 それが見つかったら3(=レバー前下)が過去に入力されていたか。 同様に2(=レバー下)が入力されていたかを調べます。

 キューを遡ってチェックするわけですが、際限なく遡ってしまうと、 ラウンド開始と同時に入力したコマンドが、タイムオーバー寸前に発動した、 なんてことも起きてしまいますから、遡るカウントを制限出来ます。
 そのためにキューチェック開始()、トリガチェック開始()などの関数が用意されています。 キューチェック開始(20)とするとレバーについては現在から 20サイクル分前のデータまでしかチェックしないようになります。

 では実際に使われている解析ルーチンを使って解説してみましょう。


   170: /*---------------------------------------------------------------------------*/
   171: void	乱舞コマンド(int p1)
   172: /*---------------------------------------------------------------------------*/
   173: {
   174: 	if(トリガ状態() && 残り体力(p1)<=22) {
   175: 	  キューチェック開始(36);
   176: 	  if(キュー存在チェック(STICK_前)) {
   177: 	    if(キュー存在チェック(STICK_後下)) {
   178: 	      if(キュー存在チェック(STICK_下)) {
   179: 	        if(キュー存在チェック(STICK_前下)) {
   180: 	          if(キュー存在チェック(STICK_前)) {
   181: 	            地上必殺技コマンド実行(ACT_乱舞);
   182: 	          }
   183: 	        }
   184: 	      }
   185: 	    }
   186: 	  }
   187: 	}
   188: }
 174行で現在のトリガの状態が0以外(=トリガの状態に何らかの変化があった)かどうかと、 残り体力が1/4以下かどうかをチェックしています。
 二つの条件をクリアしたら175行でキューチェック開始()を呼び出し、 何サイクル前までのデータをチェックするかを通知します。この場合は36サイクルです。 この数字は、大きくすると入力はしやすくなりますが暴発しやすくもなり、小さいと逆になります。 人によって、パッドの種類によっても入力のスピードには違いが出てくるので、 なかなか難しいのですが、だいたい入力要素数×5+αくらいがよいのではないでしょうか。 αはコマンドの難易度に応じて変えると。
 溜めや連打技などはまた違ってきますから、注意が必要です。
 176〜180行でそれぞれ前、後下、下、前下、前が存在するかを調べています。 キュー存在チェック()は入力が見つかると、見つかった次の位置にチェック開始位置を移すので 入力の存在した順番を間違えることはありません。

 というわけで、今回はこれで終わりにさせていただきます。 今回のサンプルには、実は致命的欠陥があります。それはコンピュータがこの技を使ってこないことです。 これは次回にフォローするつもりです。

 また、今回軽くしか触れられなかったコマンド解析ルーチンについての説明も、 次回に行なう予定です。

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