敵に連続攻撃をさせる 99/12/28

NaGu-Ru開発もいよいよ大詰め(ほんとか?)に入ってきました。 しかし、「敵がアホ」、具体的には単発攻撃しかしないので、 妙によわっちいし面白くもないということで、連続攻撃をさせることにしました。 いわゆる「こんぼ」ってやつですかね。 (実は、今までのNaGu-Ruで「敵が強い」というのは 「攻撃間隔が短い」とかそんな単純なことだったりする.....)

「コンボ」の考え方は非常に簡単です。 攻撃の一つ一つをつなげていったものがコンボなんですから。 つまり、攻撃ひとつをしおえたら、次の攻撃を行う。 たった。これだけなんですよね。 たいてい、攻撃が終わる、というのはアニメーションが終わる、ってことなので アニメーションが終わったときに、つづけて攻撃するようにしていけばいいだけです。

さて、プログラムを組むことになりますが、 コンボの長さが固定ではおもしろくないし、制限が出てきますね。 そういうわけで、長短さまざまなコンボを実現するために、TListを使うことにしました。 これに好きな数だけコンボデータ配列をぶち込んでやるわけです。 で、敵キャラクタはアニメーション終了時にそのコンボデータを読み取ったものから順番に消していき、 コンボデータがなくなったらコンボ終了、というわけです。

ちょっとした擬似的なコードを書いてみます。

var ComoboDataList:TList;
//コンボデータをセットする
procedure SetCombo(data:array of TComboData);
var i:Integer;
    comboData:PComboData;
begin 
  //引数dataの全データをリストに追加する
  for i:= Low(dat) to High(data) do begin
      New(comboData);               //メモリを確保
      comboData^:=data[i];          //データをゲット
      ComboDataList.Add(comboData); //データをリストに追加
  end;
end;

//アニメーション終了時に呼び出される関数
procedure OnAnimationLoop;
var comboData:PComboData;
    newdata:PComboData;
begin
  if comboDataList.Count = 0 then Exit;  //コンボデータがなくなったら何もしない

  comboData:=ComboDataList[0];           //先頭のコンボデータをゲット
  ComboDataList.Delete(0);               //んでリストから削除
  Dispose(combData);                     //メモリ開放

  if comboDataList.Count = 0 then Exit;  //コンボデータがなくなったら何もしない
  newData:=ComboDataList[0];             //次のコンボデータをゲット

end;
こんな感じになると思います。 ポインタとか、メモリの確保とかが絡んでいるのでわかりにくいかも しれませんが、やっていることは非常に単純です。アルゴリズムさえわかっていれば それなりに読めると思います。

それと、上記の「SetCombo」手続きで使われている、 「配列を関数に渡し、それをすべて読み込む方法」はこれ以外の場でも 多く流用できると思いますので、参考にしていただければ幸いです。

さて、実際に僕が作っているゲームでうまくいくかどうかを試してみたところ、 うまくいきました。そこで、調子に乗ってコンボデータを作っていろんなコンボを試していたら、 ある問題に遭遇しました。

どんな問題かというと、途中に攻撃以外のものをはさめないということです。 たとえば、NaGu-Ruの基本的なコンボとして、パンチX2、キックキックで敵を浮かして そこでジャンプして叩き落す、というのがあるんですが、このジャンプが問題なんです。 現在はアニメーション終了時に次のアニメーションをセットする、方法でコンボを実現していますが、 この方法だと、ジャンプは着地するまでアニメーションが続くために、着地後に 技を繰り出す、という変なものになってしまいます。つまり、ちょっとこったことをしようとすると アニメーション終了時に次のアニメーションをセットする方法では都合が悪いんです。 というか、悪かったんですよ。

というわけで、これを以下のように修正しました。
次のデータを読むタイミングを変更した。 具体的には、「現在攻撃中」というフラグを常に調べて、攻撃中でなくなれば ひとつの技が終了したとみなし、次の技を繰り出すようにした。しかし、これだとフラグの関係で ジャンプ後すぐに攻撃が出てしまうので、そのときに攻撃が当たりそうかを判定することにした。 こうすると、攻撃が当たりそうもないときは次の技にいかなくなるので、結果として 追っかけ攻撃をすることになる。

ちょっと文章で書くとわけわかんないので、コードを書いてみます。


//コンボの状態を管理する
procedure TEnemy.CheckComboState;
begin

  //コンボデータがなければだめ
  if ComboList.Count = 0 then Exit;

  //攻撃中なら次の動作に移らない
  if Attacking then Exit;
  //プレーヤーに攻撃が当たるかをチェックする
  if (NextIsAttacking)AND(not TryDstCheck) then begin
     //ミスカウントが0のときに、ミスった時間をとっておく
     if FMissCnt = 0 then FMissTime:=FrameTM;
     Inc(FMissCnt);
     Exit;
  end;

  //次のコンボデータを読み込む
  ComboList.DelData(0);
  if ComboList.Count = 0 then Exit;

  //ミスったときから1秒以上ならコンボ失敗なのでコンボデータをクリアする
  if (FMissCnt > 0)AND(FrameTM - FMissTime > 1000) then begin
     ComboList.ClearAll;
  end else begin
  //それ以外ならコンボを続ける
     doAction(ComboList.Data[0]);
     FMissCnt:=0;
  end;

end;
スパゲッティであるNaGu-Ruのコードの中でかなりきれいなほうです。 多分読めると思います...(たぶんね)。上の条件に加えて「コンボミス」を判定して よりそれっぽくしてます。これで、敵が普通にコンボをかましてくれるようになりました(^^;) もうこれで、思考ルーチンがへぼいなんていわせないぜ!!(<まだへぼいやん) さぁ、みんなもれっつれっとこんぼ!!

※敵がアホ
安いゲームにありがちな「強い=反応速度が速い」という方程式の元 ゲームが作られていたためにおこった現象。格闘ゲームでこれをやると、 かなり面白くないものになる。幸いNaGu-Ruは複数の敵と戦うので、 ある程度ごまかしが効いていたのだ。

※アニメーション
ここでは絵だけでなく値が変化することをさす。 数字が変わっていくのだって立派なアニメーションだ(ほんとか?)。

※擬似的なコード
なんとなく感じをつかんでもらうためのコード。 当たり前だけどこれだけじゃなんにもなりません。

※ポインタ
初心者プログラマの敵で、「アドレス違反」と手を組んで プログラマに襲い掛かる強敵。

※アルゴリズム
物事が成り立っている仕組みのことだと思うし、僕はそう思っている。

※TList
DELPHIのVCLの中でも1、2を争うほど(ぽくむらランキングによる)便利なクラス。 VCL内部でも多用されている。 実際には可変長配列といううわさもあるが、事実は知らない。

※High,Low関数
これを使うことによって 引数として渡された配列の全要素にアクセスすることができる。 詳しくはヘルプ参照のこと。

※リスト
複数のデータを管理するやつ。 たとえば、TListなどもこれのひとつだと思う。

※うまくいくかどうか
横文字でいうとデバッグのこと。コーディングに匹敵する、またはそれ以上の時間をかける必要があるときもある。

※都合が悪い
うまくいかないこと。プログラムに修正が必要ってこと。

※コンボデータ
実際にはただの数字の集まり。これをプログラムがある技としてとらえるようになっている。

※ジャンプ
ぴょんぴょん飛び跳ねること。二代目NaGu-Ruではジャンプのアニメーションがついた(前作ではなかった、というか 空中を歩いているという、とんでもないものだった)

※フラグ
「その条件は成立するか!?」ってことを表すプログラム用語。 学校とかでもよく使われる。PASCALの型でいうとBoolean型(TrueかFalseってやつね)にあたる。

※FrameTM
ごめん、これも俺プログラムの仕様です、ごめんなさい。 なにかというと、現在の時間です。オブジェクト管理人が1Frameにつき一度だけ GetTickCountによって時間をゲットし、それをオブジェクトたちが読み取る、という仕組みだ。 GetTickCountも繰り返し呼ぶと結構時間を食うものなんですよ。

※NextIsAttacking
ごめん、これまた俺プログラムの仕様。なにかというと、 次のコンボデータが攻撃データかを調べる関数です。ちょこっとソースコードの解説を 入れると、次のコンボデータが攻撃データで攻撃が当たりそうにないならコンボ中断、ってことです。 次のコンボデータがジャンプとかなら攻撃データじゃないから即呼び出されるようになってるんです。

※クリア
ここではゲームの「ステージクリア」をさすのではなく、 データを全消しすることをさす。

※スパゲッティ
プログラム用語。ソースコードがこんがらがっている様子。 スパゲッティみたいってこと。一般的にあまりいい言葉ではないが、僕はよくこの状態に陥る。

※思考ルーチン
読んで字のごとく、何かを考えるルーチン(手続きとか関数とか)。 コンピュータと対戦型のゲームには、これが必要になることが多い。 プログラムとして面白いテーマなので、C Magazineなどで特集されることが多い。 興味のある人はぜひ目を通してみてほしい。人工知能の仲間だと思う。






もどる