2014年9月23日火曜日

DRN.006 ボンバーマンの行動パターンを考える。

まえがき

連載第4回はボンバーマンを取り上げる。ボンバーマンといってもハドソンのアレではない。というか今やハドソンって影 (会社) も形 (ブランド) もなくなってしまったのか。何てことだ。ボンバーマンはスーパーファミコンで出た『スーパーボンバーマン』、『スーパーボンバーマン 2』、『スーパーボンバーマン 3』、『ぱにっくボンバー W』のソフトウェアだけでなくボンバーマンの顔のデザインのスーパーマルチタップ2も持っていたし、裏ステージを出すためだけにスーパージョイカードも買ったというのに。その他にも、ハドソンのソフトウェアでは『高橋名人の冒険島 Ⅱ』、『スーパー桃太郎電鉄 DX』、『アースライト 2・ルナストライク』、『ゲームボーイウォーズ 3』、『NECTARIS』のPlayStation 版、iPhone 版も買ったナー。脱線が過ぎた。

ボンバーマン本体の行動パターン

  • 一定確率で
    1. ハイパーボムを投げる。
    2. 一定確率でさらにハイパーボムを投げる。
    3. 一定確率でさらにハイパーボムを投げる。
  • 一定確率で
    1. 自機に向かって小ジャンプ
  • 一定確率で
    1. 自機に向かって大ジャンプ
  • 被弾したとき
    1. 一定時間無敵になる

ハイパーボムの行動パターン

  • 発射されたとき
    1. 自機狙い、放物線運動
    2. 接地すると爆発

ジャンプをこれにも流用できそうだ。

攻略法

ボンバーマンの小ジャンプ、大ジャンプは下をくぐれるので端に追いつめられないように適宜下をくぐって切り返す。ハイパーボムの爆風の範囲が広いので x 座標を大きくずらすと躱しやすいが、連続で投げてくることもあるので大きく逃げ過ぎて端に追いつめられないように。タイミングがよければジャンプでも避けられる。

ワイリーステージ4はワープカプセルがあって部屋が狭い。ワープカプセルの外に出て同様にする。

あとがき

ジャンプの大小は自機との距離だトカ被弾しただトカが関係していそうな気もするんだけどよくわからんので確率依存にした。困ったときは確率依存、確率依存万能説。あー、でもそれにしたおかげで内心「こっちくんな」と思いながら戦うことになるので業が深いな。このボンバーマンには関係ないけども。

2014年9月22日月曜日

DRN.005 アイスマンの行動パターンを考える。

まえがき

連載第3回はアイスマンを取り上げる。パターンが見え見えなので余り面白くないな。

アイスマン本体の行動パターン

  • 非負の整数を k として、移動した回数 n が n = 4 * k または n = 4 * k + 3 のとき
    1. 右に向かって前進
  • 非負の整数を k として、移動した回数 n が n = 4 * k + 1 または n = 4 * k + 2 のとき
    1. 左に向かって前進
  • 移動した後
    1. ジャンプ
    2. 自機の方を向いて等速で下降しながらアイススラッシャーを発射 * 3
    3. 自機の方を向いて等速で上昇しながらアイススラッシャーを発射 * 3
    4. 落下
  • 被弾したとき
    1. 一定時間無敵になる

各行動パターンが単純でパターン変更に確率が関係ない。パターン変更が確率だけに依存するガッツマンよりもさらに大味な感じがする。ジャンプ・落下は放物線運動、上昇・下降は等速直線運動を表現したつもり。ジャンプの頂点、着地点でパターン切り替えれば OK ね。

アイススラッシャーの行動パターン

  • 発射されたとき
    1. 直進
    2. 6 * n 発?撃つごとに弾速がアップ

攻略法

アイスマンステージでは床が滑る床なので多少動かしづらいが、慣れれば後方に滑って距離をかせぎながら前方に攻撃できる。部屋の端でア イススラッシャーをかわしながら 攻撃するだけ。アイススラッシャーは下をくぐれるが上を飛び越えることができない上段、下をくぐることもできれば上を飛び越えることもできる中段、下をくぐれず上を飛び越えるしかない下段の3種類があり、上段、中段、下段、下、中段、上段の順に撃ってくるので、適当にかわす。アイススラッシャーは撃てば撃つ程速くなるので早期に決着をつけるとよい。

ワイリーステージ4では床が滑らず、 ワープカプセルがあって部屋が狭い。ワープカプセルの外に出て同様にする。

あとがき

AI 的にはかなり単調なアイスマンだが、アイススラッシャーは高火力なので意外と強敵。アレだ。力こそパワー。

2014年9月21日日曜日

DRN.004 ガッツマンの行動パターンを考える。

まえがき

連載第2回は DRN.004 ガッツマンを題材にする。便宜上、自機が特殊武器スーパーアームで持ち上げられるブロックをガッツブロック (P) 、ガッツマンが投げるブロックをガッツブロック (E) とする。

ガッツマン本体の行動パターン

  • 一定確率で
    1. 前方にジャンプ
    2. 着地時に地震が発生し、地上にいる自機を転ばす。
    3. 揺れが収まるまで待機。
  • 一定確率で
    1. 後方にジャンプ
    2. 着地時に地震が発生し、地上にいる自機を転ばす
    3. 揺れが収まるまで待機。
  • 一定確率で
    1. 垂直にジャンプ
    2. 着地時に地震が発生し、地上にいる自機を転ばす
    3. 揺れが収まるまで待機。この間にガッツマンの頭上の天井からが降ってきたガッツブロック (E) を受け止める
    4. 自機を狙ってガッツブロック (E) を投げる
  • 被弾したとき
    1. 一定時間無敵になる

行動パターンの切り替えは確率だけなので単純。一連の行動が終わったらフラグを立てて、フラグが立っていたら乱数を生成してその値に応じて状態遷移。カットマンの場合、ジャンプの頂点でローリングカッターをねじ込む必要があったが、ガッツマンでは着地時に地震等をねじ込む必要がある。その他、被弾したときも一定時間無敵になるのは同じだが、行動パターンには影響がないので、前3つのパターンとは別レイヤーになっているとでもいうべきか。カメラを揺らしたり、自機を転ばしたりと、本体とその武器以外に直接影響を与えるのも特徴だ。

ガッツブロック (E) の行動パターン

  • ガッツマンが垂直にジャンプしたとき
    1. ガッツマンの頭上の天井から落下
  • ガッツマンに投げられたとき
    1. 自機に向かって直進
  • 地面に当たったとき
    1. 4つの破片に分かれ、破片のうちの2つは水平方向に直進し、残り2つは斜め上45°の方向に直進する

攻略法

ガッツマンステージではシャッター際に高台とガッツブロック (P) があるのでこの上で戦うとよい。地震はガッツマンが着地した瞬間からしばらくの間続くので、この間ジャンプで空中にいれば巻き込まれない。もちろん地震に巻き込まれないようにジャンプできるならそのほうがよいが、下手にジャンプすると地震で転ぶのが遅れ、転び状態から復帰するのも遅れてガッツブロック (E) や体当たりでダメージを受けてしまうので、ガッツマンとの距離があるときは敢えてジャンプしないで最速で転び復帰するのも手だ。ガッツブロックはガッツマンの頭の高さから自機の足下に向かって飛ぶ。高台やガッツブロック (P) の上にいると画面中央側のガッツブロック (P) の際で分裂することが多いのでシャッター際よりはある程度画面中央に寄ったほうがよい。ガッツマンに距離を詰められるかどうかは運なのだが、一応ガッツマンのジャンプをくぐることはできる。

ワイリーステージ4には高台等がないので前述の戦法は使えない。まずは天井の低いカプセルの外に出て高くジャンプできるようにする。地震は上と同様に対応する。ガッツブロック (E) が地面に当たって分裂するポイントでジャンプするとかわしやすいわけだが、ガッツブロック (E) は自機の足下狙いなのでふつうにタイミングジャンプすればいいことが多い。部屋が狭くなっている分、プレイヤーの運が試される (ぉ

あとがき

ガッツマンのように単純なパターンでパターン変更も完全に確率で制御にだとものすごく大味な気がするな。ジャンプは頂点と着地時にフックポイントを仕込んでおけばカットマンとガッツマンで共通化できそうだ。

2014年9月18日木曜日

DRN.003 カットマンの行動パターンを考える。

まえがき

2Dアクションゲームの敵の AI とでもいうべきものを作りたい。これまで赤ノコノコ等比較的単純なものをつくってきたが、もっと複雑なものをどうやって作るのか。これまで同様の単純な行動パターンの上位に、条件に応じてどれを実行するかを指示する制御があればいいかな。

面白い行動パターンがすぐに思いつくわけもないし、制作中のそれをここでネタバラシしてしまうのもアレなので、『ロックマン』シリーズのボス敵がどうなっているのか取り上げよう。1-7 まではやったから、これでしばらくはネタに困らないね。ヤッター。というわけでカットマン (FC版) の行動パターンを分析してどういう制御をすればいいのか考える。 当然のことながら、以下の行動パターンは外観上でしかないので実際とは違う。悪しからずご了承ください。

カットマン本体の行動パターン

  • 自機との距離 (X軸) が一定以上あるとき
    1. 自機に向かって歩く。
  • 自機との距離が一定未満のとき
    1. 軌道修正できないジャンプ。
    2. ジャンプの頂点付近で画面上にローリングカッターがなければ、一定の確率? でローリングカッターを投げる。
  • 被弾したとき
    1. 行動をキャンセルしノックバックし、短時間無敵状態になる。
    2. 立ち止まって、チョッキンチョッキン。
    3. 画面上にローリングカッターがなければローリングカッターを投げる。

大きく分けて 3 つのパターンがあり、自機との距離、被弾がトリガー。思いつきだけで KS コードを書いてみる。


using UnityEngine;
using System.Collections;

public class CutMan: MonoBehaviour
{
  bool counterAttackFlag = false;

  // 画面外のどこかにローリングカッターを隔離
  Vector3 positionOfOuterScreen = - Vector3.one;

  // Jump, Walk 切り替えの閾値
  float xThreshold = 5;
  Transform thisTransform;
  Transform playerTransform;
  enum ActionPattern
  {
    CounterAttack,
    Jump,
    Walk,
  };
  ActionPattern currentActionPattern = ActionPattern.Walk;
  bool switchActionPatternFlag = false;

  void Start ()
  {
    thisTransform = transform;
    playerTransform = GameObject.FindWithTag ("Player").transform;

  }

  void FixedUpdate ()
  {
    if (switchActionPatternFlag) {
      SwitchActionPattern ();
    }
    DoActionPattern ();
  }

  void SwitchActionPattern ()
  {
    Vector3 displacementFromThisToPlayer = playerTransform.position - thisTransform.position;
    float xDistance = Mathf.Abs (displacementFromThisToPlayer.x);
    float xDistanceMinusXThreshold = xDistance - xThreshold;
    
    if (counterAttackFlag) {
      currentActionPattern = ActionPattern.CounterAttack;
      counterAttackFlag = false;
      switchActionPatternFlag = false;
    } else if (xDistanceMinusXThreshold >= 0) {
      currentActionPattern = ActionPattern.Walk;
      switchActionPatternFlag = false;
    } else {
      currentActionPattern = ActionPattern.Jump;
    }
  }

  void DoActionPattern ()
  {
    switch (currentActionPattern) {
      case ActionPattern.CounterAttack:
        CounterAttack ();
        break;
      case ActionPattern.Jump:
        Jump ();
        break;
      case ActionPattern.Walk:
        Walk ();
        break;
    }
  }

  void CounterAttack ()
  {
    // ImplementMe
  }

  void Jump ()
  {
    // ImplementMe
  }

  void Walk ()
  {
    // ImplementMe
  }

  void OnTriggerEnter2D (Collider2D other)
  {
    if (other.tag == "PlayerBullet") {
      counterAttackFlag = true;
      switchActionPatternFlag = true;
    }
  }
}

パターン別のスクリプトを作って、その .enabled をこのスクリプトのswitch 文中で呼ばれるメソッドから操作するように書けばいいかな。色々書き足りないけどまた後日にでも加筆するとしよう。

ローリングカッターの行動パターン

カットマンの武器、ローリングカッターの外観上の行動パターンを以下に示す。例によって外観上のパターンで実際とは異なる。

  1. 自機狙いの射角で画面端まで直進
  2. 本体狙いで角度修正しつつ移動。本体が撃破されたときはそのまま直進。
  3. 本体に接触すると消滅

こんなところか。疲れたのでこっちは全部省略 (ぇ。

攻略法

せっかくだから攻略法も書いておく。どうやったら倒せるかも考えておかないと、『カイザーナックル』のジェネラルみたいに好き放題やったら、普通の人には倒せなくなってしまう。ところで『カイザーナックル』はいつ家庭用ゲーム機に移植されるんだろう。

カットマンステージのボス部屋には高台とガッツブロックがある。高台のシャッター際に陣取り、ローリングカッターをジャンプでかわしながら、飛びかかってきたカットマンを撃ち、下の床に落とす。自機の近くまで飛び込まれた場合、カットマンが下の床に落ちないで高台に残ることがあるが、被弾後の無敵時間終了直後に被弾させるようにしてたたき落とす。

ワイリーステージ2の道中の小部屋には高台がないので前述の戦法は使えない。行動キャンセル狙いでカットマンを攻撃。敵の無敵時間中にチョッキンチョッキンしはじめるので、無敵解除直後にヒットするのを狙って攻撃しつつ、行きのローリング・カッターを (自機が画面際の場合、画面中央へ向かって) ジャンプしてかわす。帰りのローリング・カッターはジャンプしたカットマンに連られて軌道修正されるので垂直ジャンプではなくカットマンから遠ざかるようにジャンプしてかわす。

2014年9月13日土曜日

配列色々

参考資料 1を読んで、なるほどと思いつつも、私は List<T> を余り使っていないのだった。反論でも何でもなく、ただ単に要素数が固定のものばかりを扱っているだけだけなので、可変のものを扱う必要があれば使うだろう。ArrayList を使わないのも同意する。

今、制作中のゲームでは type[] と SortedDictionary<TKey, TValue> をよく使う。それらの使い分けは、要素数が固定かつキーが整数でも分かりやすいもの、スコープが狭く他から参照しないもの、インスペクターから値を入れたいものには type[] を使い、それ以外には SortedDictionary<TKey, TValue> を使うようにしている。キーが整数でも分かりやすいというのは、格納した値を参照するために type[0], type[1], ... と書くときに適切なキーがわかるということだ。

キーが整数でも適切なキーが分かりやすい例は座標の履歴を格納する場合で、数字の若い方が新しいのか古いのかの流儀はあるにしても自分の分かりやすい方にすればいい。なお、私は若い方が新しい流儀である。

他方、キーが整数だと適切なキーが分かりにくい例は東西南北に対応するベクトルを配列に格納する場合だ。私がやるとすれば北を 0、東を 1、南を 2、西を 3 にするだろうが、読者の皆さん (誰?) も果たして同じだろうか。同じだったらボクと握手。

キーに凝るのは余所から参照するときにわかりやすくするためなので、スコープが狭く他から参照しないものにまで凝ったキーを付ける必要はない。もう一つの理由の、インスペクターから値を入れたいというのは、List<T>, SortedList<T>, Dictionary<T>, SortedDictionary<T> ではできないからという消極的な理由だ。

なぜ Dictionary<TKey, TValue> でなく SortedDictionary<TKey, TValue> なのかは参考資料 2 が詳しいのでそちらをご参照下さい。

さて、SortedDictionary<TKey, TValue> では型で束縛されるが好きなキーが使えるので分かりやすいキーにしやすい。キーを string にすれば制限はほとんどなくなるが、補完が効かないので私は要素数が固定の場合はキーを Enum にする。いやぁ、補完が効くって素晴らしい。

あ、書き忘れていたが、SortedDictionary<TKey, TValue> や Dictionary<TKey, TValue>を使うには以下を書いて名前解決しておくとよい。

using System.Collections.Generic;

参考資料

  1. Myouji. "[Unity] Array/配列的な奴らとの付き合い方-type[]とかListとか". myoujing!!. 2014-08-24. http://myoujing.wpblog.jp/2014/08/874/, (参照 2014-09-11)
  2. Vladimir Bodurov. "IDictionary Options - Performance Test - SortedList vs. SortedDictionary vs. Dictionary vs. Hashtable". Vladimir Blog. 2007-12-24. http://blog.bodurov.com/Performance-SortedList-SortedDictionary-Dictionary-Hashtable/, (参照 2014-09-11)

2014年9月11日木曜日

賽の河原で小石を積み上げる単純作業

やはり雑談よりは開発ネタを書くべきなのか。記事を書かない日も KS コードを書いては調整と見直しをやっている。どのように動いて欲しいかは割とハッキリしているが、プログラミング自体は手探りなので、できたコードは KS ばかりで手戻りが多い。とはいえ、例え KS コードでも少なくとも 2, 3 作ってみないことには多少なりとも再利用できそうなものの作り方が見えてこないのでやるしかない。

制作中のゲームでは、今のところ、キー入力に応じて動くプレイヤー、行き先の足場がなくても進む、壁際で反転する敵 (マリオにおける緑ノコノコに相当)、行き先の足場がないまたは壁際で反転する敵 (マリオにおける赤ノコノコに相当)、行き先の足場がないとジャンプ、壁際で反転する敵 (何かあったっけ?) 等を作った。もっと複雑な行動パターンを作りたい。

なお、キャラクターの座標計算には、下記のサイトを参考に、velocity Vertlet 法を用いた。

参考資料

  1. みその計算物理学. "velocity Verlet法(PDF形式)". みその計算物理学. http://www.geocities.jp/supermisosan/moleculardynamics/velocityverlet.pdf, (参照 2014-09-10)
  2. T. Koishi. "数値積分の実行". Koishi's Page. http://polymer.apphy.u-fukui.ac.jp/~koishi/lecture/md_program5/, (参照 2014-09-10)

2014年9月8日月曜日

弾切れ

メーデーメーデーたまぎれすんぜん、どころか書き溜めておいた (という程の量ではないが) 記事は全て放出してしまったので真に弾切れだ。間があきすぎてもアレなので弾切れの話をしてお茶を濁そう。

ゲームで武器が弾切れでクリア不能なんていうのはやだなートカ思う。AC2 でレオス・クラインを倒したのに軌道コントロール装置を破壊する弾が足りないトカ、AC3 で管理者部隊AC を倒したのに管理者を破壊する弾が足りないトカね。私が好きな『ロックマン』シリーズのうち 2, 3, 4はラスボスに弾数無限の通常武器が通用せず、エネルギー制限のある特殊武器しか効かないのでまさに弾切れで詰む要素のあるゲームだ。でも案外気にならなかったのは無限コンテニューがあり、自殺してやり直すことができたからだろう。エネルギーが尽きても残機があればそれが尽きるまで自殺を強いられるわけではあるが。同シリーズの後発の作品 (例えば『イレギュラーハンターX』) では1機ミスすれば武器エネルギーは全回復するようになっており、さらに後発の作品 (同シリーズではないが開発者的な意味で同じ系統に属する『蒼き雷霆ガンヴォルト』) ではエネルギー切れになる前ならいつでもチャージ可能でさらにエネルギー切れになっても時間が経てば回復するという具合であり、エネルギー切れに陥っている時間は短くなっている。これなら弾切れで面倒なことになるのが嫌いな人でもOKよね。