2014年9月25日木曜日

DRN.008 エレキマンの行動パターンを考える。

まえがき

連載第6回はエレキマンを取り上げる。ちょっとよくわからない。

エレキマン本体の行動パターン

  • 非負の整数を k として、移動した回数 n が n = 4 * k のとき
    1. 部屋の右側へ移動
  • 非負の整数を k として、移動した回数 n が n = 4 * k + 1 または n = 4 * k + 3のとき
    1. 部屋の中央に移動
  • 非負の整数を k として、移動した回数 n が n = 4 * k + 2 のとき
    1. 部屋の左側に移動/li>
  • 特定の x 座標に到達したとき
    1. サンダービームを発射
  • 自機がショットしたとき
    1. ジャンプする。
  • 壁に接触したとき
    1. ジャンプする。
  • 被弾したとき
    1. 行動をキャンセルし、ノックバック
    2. 一定時間無敵になる

こちらから攻撃しない限りはアイスマンと同様に部屋の右側、中央、左側を行ったり来たりしながら、指定 x 座標に到達するとサンダービームを発射する。ガッツブロックに引っかかったり、ノックバックさせたりで指定座標への到達できないとくり返し同じ座標に向かおうとするので、どうやら指定座標に到達してはじめて次の目標地点が変わるようだ。

サンダービームの行動パターン

  • 発射されたとき
    1. 直線運動
    2. 自機に当たると拡散

攻略法

サンダービームの予備動作中にタイミングよくショットを当てると、ハメられる。エレキマンステージ、ワイリーステージ2の両方で可能。動画を参照。

Defeat ELEC MAN without walking, jumping from hide behind on Vimeo.

あとがき

ハメると初期位置から一歩も動かずに撃破することもできるが、高火力のアイススラッシャーを上回る超火力のため事故りやすい。ノックバックするのが唯一の救い。

2014年9月24日水曜日

DRN.007 ファイヤーマンの行動パターンを考える。

まえがき

連載第5回はファイヤーマンを取り上げる。fireman は消防士のことなんだが、コイツは廃棄物処理ロボットである。

ファイヤーマン本体の行動パターン

  • 自機との距離が一定以上のとき、一定確率で?
    1. 自機の方を向いてファイヤーストームを撃つ
  • 壁に接触したとき
    1. 向き反転
  • 被弾したとき
    1. 一定時間無敵になる
    2. 自機の方を向いてファイアーストームを撃つ
  • 上記以外のとき
    1. 移動

離れているとファイヤーストームを撃ってくるが、間合いを詰めたままだと攻撃しない限りは移動するだけである。攻撃すると ファイヤーストームで反撃する。

ファイヤーストームの行動パターン

  • 発射されたとき
    1. 自機狙い、直進
    2. 自機足下に火がしばらく残る。

攻略法

ファイヤーマンステージでは、ある程度距離を詰めてファイヤーストームを封じつつ、ショットとジャンプを同時押しでジャンプショット、着地直後にまたジャンプショットのくり返しでハメられる。

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

Defeat FIRE MAN with ROCK BUSTER from hide behind on Vimeo.

あとがき

攻撃面ではとにかく撃ちまくればよかった他のボスに対するアンチテーゼとでもいう位置づけか、弱点を衝けばゴリ押し可能なこのゲームでは珍しく、弱点を衝いてもパターンを無視してゴリ押ししようとすればゴリ押し返されることがある。また、なるべく敵から離れるという定石が裏目に出るのも嫌らしい。

2014年9月23日火曜日

ミサイルは渡り移るもの。

『ロックマン』の6ボスまで予約投稿にしたのでここで一段落。制作中のゲームの動画をさらす。

ミサイル渡りjump from one missile to another. from hide behind on Vimeo.

ミサイルからミサイルへ飛び移っているのは全部敵キャラクターなのだが、敵キャラクターのグラフィックがまだないのでプレイヤーので代用。プレイヤーキャラクターはミサイルにぶら下がっているやつ。ぶら下がり状態で左右に移動できるのだが、グラフィックがまd (以下略)。爆煙やらバックファイアを書きたいが、デカいのを書くと目障りになってしまうのをどうしたらよいかな。

そんなつもりはなかったが、クラウド・ストライフと同じようなカラーリングになっちゃった。イメージイラストもなくいきなり雑なドット絵打っているので、こうしてさらしておいたらエレガントなイラストを誰か書いてくれたりしませんかネェ。プレイヤーのドット絵もまだまだ足りない。被弾時のとぶら下がり移動、壁張り付きと壁張り付き移動は 最低でも必要だ。

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よね。

2014年9月6日土曜日

下からすり抜けて上に乗ることができる床の実装例 (Unity C#) Nostalgia (Version 1.0.2) 対応版

Nostalgia (Version 1.0.2) では前回示したようなコライダーごとにスクリプトを付ける手法は使えないようなので、プレイヤーの近傍のセルを調べてcollider.enabled を切り替える方法にした。

図で示すとこういう感じ。画像の赤色のセルはプレイヤーのいるセル、緑色のセルは、中にコライダーがあれば collider.enabled = false にするセル、青色のセルは、中にコライダーがあれば collider.enabled = true にするセルを示す。

KS コードを公開するよ。コメントをつけたやたらに長い名前の3つの変数の値をお好みで適当にいじればいいんじゃないかな。


using UnityEngine;
using System.Collections;
using Nostalgia;

public class NostalgiaColliderEnabledSwithcher : MonoBehaviour
{
  Transform playerTransform;
  Map map;
  Vector3 playerPosition;
  int numberOfHalfOfHorizontalCells;

  // 中にコライダーがあれば collider.enabled の値を変更するセル (図の青色または緑色のセル) の水平方向の数。
  int numberOfHorizontalCells = 7;
    
  // 中にコライダーがあれば collider.enabled = false にするセル (図の緑色のセル) の垂直方向の数。
  int numberOfVerticalCellsEachOfWhichMayHaveColliderToBeDisabled = 6;
    
  // 中にコライダーがあれば collider.enabled = true にするセル (図の青色のセル) の垂直方向の数。
  int numberOfVerticalCellsEachOfWhichMayHaveColliderToBeEnabled = 1;
  Point2[] offset1;
  Point2[] offset2;

  void Start ()
  {
    playerTransform = GameObject.FindGameObjectWithTag ("Player").transform;
    map = GetComponent <Map> ();
    numberOfHalfOfHorizontalCells = (numberOfHorizontalCells - 1) / 2;
    offset1 = new Point2[numberOfHorizontalCells * numberOfVerticalCellsEachOfWhichMayHaveColliderToBeDisabled];
    for (int i = 0; i < numberOfHorizontalCells; i++) {
      for (int j = 0; j < numberOfVerticalCellsEachOfWhichMayHaveColliderToBeDisabled; j++) {
        offset1 [numberOfVerticalCellsEachOfWhichMayHaveColliderToBeDisabled * i + j] = new Point2 (i - numberOfHalfOfHorizontalCells, j);
      }
    }

    offset2 = new Point2[numberOfHorizontalCells * numberOfVerticalCellsEachOfWhichMayHaveColliderToBeEnabled];
    for (int i = 0; i < numberOfHorizontalCells; i++) {
      for (int j = 0; j < numberOfVerticalCellsEachOfWhichMayHaveColliderToBeEnabled; j++) {
        offset2 [numberOfVerticalCellsEachOfWhichMayHaveColliderToBeEnabled * i + j] = new Point2 (i - numberOfHalfOfHorizontalCells, - 1 - j);
      }
    }
  }
  
  void FixedUpdate ()
  {
    playerPosition = playerTransform.position;
    Point2 mapPointOfPlayerPosition = map.WorldPointToMapPoint (playerPosition);

    Point2 [] listOfPoint2InCellWhichMayHaveColliderToBeDisabled = new Point2[numberOfHorizontalCells * numberOfVerticalCellsEachOfWhichMayHaveColliderToBeDisabled];
    for (int i = 0; i < numberOfHorizontalCells; i++) {
      for (int j = 0; j < numberOfVerticalCellsEachOfWhichMayHaveColliderToBeDisabled; j++) {
        listOfPoint2InCellWhichMayHaveColliderToBeDisabled [numberOfVerticalCellsEachOfWhichMayHaveColliderToBeDisabled * i + j] = mapPointOfPlayerPosition + offset1 [numberOfVerticalCellsEachOfWhichMayHaveColliderToBeDisabled * i + j];
      }
    }
    foreach (Point2 p in listOfPoint2InCellWhichMayHaveColliderToBeDisabled) {
      SwitchColliderEnabled (p, false);
    }

    Point2[] listOfPoint2InCellWhichMayHaveColliderToBeEnabled = new Point2[numberOfHorizontalCells * numberOfVerticalCellsEachOfWhichMayHaveColliderToBeEnabled];
    for (int i = 0; i < numberOfHorizontalCells; i++) {
      for (int j = 0; j < numberOfVerticalCellsEachOfWhichMayHaveColliderToBeEnabled; j++) {
        listOfPoint2InCellWhichMayHaveColliderToBeEnabled [numberOfVerticalCellsEachOfWhichMayHaveColliderToBeEnabled * i + j] = mapPointOfPlayerPosition + offset2 [numberOfVerticalCellsEachOfWhichMayHaveColliderToBeEnabled * i + j];
      }
    }
    foreach (Point2 p in listOfPoint2InCellWhichMayHaveColliderToBeEnabled) {
      SwitchColliderEnabled (p, true);
    }
  }

  void SwitchColliderEnabled (Point2 givenPoint2, bool b)
  {
    Cell cellOfGivenPoint2 = map.GetCell (givenPoint2);
    if (cellOfGivenPoint2 != null) {
      Collider2D colliderOfCell = cellOfGivenPoint2.collider;
      if (colliderOfCell != null) {
        colliderOfCell.enabled = b;
      }
    }
  }
}

使用した Nostalgia の Version 1.0.2 を追記した。 2014-09-25

2014年9月4日木曜日

Unity 用タイルマップエディタ AutoTileSet (Version 1.3) と Nostalgia (Version 1.0.2) の比較

  1. 背景
  2. AutoTileSet の特徴
  3. Nostalgia の特徴
  4. 両者の比較

1. 背景

Unity で作成中のゲーム (2D ジャンプアクション) でマップを作成するに当たり、手作業で床、壁、足場等のプレハブをシーンビューに配置したところ、非常にダルかった。それで、テキスト (CSV, TSV, XML等) でタイルマップのデータを作成し、これを読み込んで表示する、ということを検討したところ、Tiled Map Editor (http://www.mapeditor.org/) を使うことで画像の作成は簡単だったが、画像とは別にコライダーやレイヤーの情報を手入力する必要があるため非常に面倒で、Unity 内で作業が完結しないのもいまいちだった。

そこで、 Asset Store にて Unity のシーンビューで直接タイルマップの作成が可能なアセットを検索したところ、いくつか見つかった。本稿では、その中で無料であった AutoTileSet (Version 1.3)と、RPGツクールVX規格またはWOLF RPG Editor規格のオートタイルに対応していると見てとれた Nostalgia (Version 1.0.2) を取り上げ比較した。

2. AutoTileSet の特徴

長所

  • 無料なので資金に余裕の無い個人開発者にとってはありがたい。
  • ペンと消しゴムの切り替えが自動。シーンビューでタイルがないところをクリックするとタイルを配置でき、タイルがあるところでは消去できる。また、ドラッグすると最初のクリック時の動作をし続ける。
  • ヒエラルキーのAutoTileSet のプレハブ以下に各タイルに対応するタイルの名前の GameObject が生成される。それにスクリプトをアタッチして色々できる。
  • 段差があるところを段々にするか斜面にするか切り替えることができる。
  • タイルにバリエーションをもたせることができる。元々のタイルセットに加えて1つ以上のバリエーションをもつときにタイルを配置するとそれらの中のいずれかのタイルセットから適切なタイルが配置される。これにより見た目に単調なマップにならずに済む。
  • サンプルシーンの見栄えがよい。洞窟に光が差し込んでいる感じが出ている。

短所

  • ツールがないので、まっすぐな線を引くのがむずかしく、塗りつぶしするのも面倒。
  • AutoTileSet用のオートタイルセットは48パターンも必要なので素材を自作するのはちょっと大変かも。せっかくあるバリエーションの利点を殺しかねない。同じ開発元の AutoTileGen (Steam等 にて $25 くらい?で販売中) を使えば5パターン (うち2パターンはマスク) の画像を元に48パターンの画像からなるタイルセットを生成できるようだ。
  • サポートが英語。他の外国語よりはずいぶんマシだが。

Nostalgia の特徴

長所

  • ペン、消しゴムの他、矩形範囲のタイル配置 / 消去、塗りつぶしツールがある。
  • RPGツクール VX または WOLF RPG Editor の形式のオートタイルを扱える。
  • 作者が日本人なので日本語でサポートが受けられる。
  • Refreshボタンがあり、タイルセットを更新したときに再描画できる。シーンを複製してタイルセットだけを総換えする等でマップの再利用に便利。

短所

  • 矩形ツールまたは塗りつぶしツールの適用範囲が広いともたつき、広過ぎると最悪 Unity が応答無しになった。死んでもいいプロジェクト等でその辺の感覚をつかんでおいたほうがよい。
  • サンプルシーンが微妙。
  • 斜面のサポートはないようだ。今のところ使う予定はないけど。

その他

  • Map以下の構造 (Cell, Collider等) はインスペクター上では隠蔽されているが、スクリプトでアクセスする手段は用意されているのでそちらを使うべし。作者さんに教えてもらうまで気付いていなかったが、使うの自体はむずかしくない。

4. Nostalgia と AutoTileSet の比較

以下に比較の表を示す。

表. Nostalgia と AutoTileSet の比較
項目 Nostalgia AutoTileSet コメント
ペン Nostalgiaのペン先の矩形の大きさは可変。
消しゴム Nostalgiaの消しゴム先の矩形の大きさは可変。
矩形選択配置 × Nostalgiaの大量配置に難あり。
矩形選択消去 × Nostalgiaの大量消去に難あり。
塗りつぶし × Nostalgiaの大量配置に難あり。
Refresh × あるとマップの再利用等に便利。
バリエーション × あるとマップが視覚的に単調になりにくい。
1タイルセットのパターン数 5 48 少ないほうが素材の自作は楽。
サポート言語 日本語 英語 英語でも可だが、母国語である日本語の方がいい。英語以外の外国語は辛い。
斜面 × -
例は例でしかないが、例買いもある?
価格 20$ 0$ -

◎: よい, ○: わるくない, ▲: よくない, △: わるい, ×: 機能なし.

RPGツクールVX規格またはWOLF RPG Editor 規格のオートタイルに対応という時点で Nostalgia 以外の選択肢がなかった (あるいは筆者が分からなかっただけで実はどこかにあったのかもしれないが、分からないものはないのと同じ)。素材を自作する場合でもわずか5タイルのパターンを作成すればよく、そうでない場合でもフリー素材が利用可能なので、両規格に対応していることはかなりのメリットだと思う。タイルセットのコンバーターを書くのは比較的容易だと思われるが、都度別途コンバートするのは面倒。元から対応していればその面倒はない。AutoTileSet の方が優れている項目のうち斜面、例は今回は重視しない機能だったので不問、バリエーションはあればよかったカモというところ。それよりも矩形選択配置または消去、塗りつぶしの重さが課題か。

機能の優先順位は人それぞれなので、自分の要求にあったものを使えばいいと思うよ。事実誤認トカありましたら教えてください。

両者のバージョン (AutoTileSet (Version 1.3)と Nostalgia (Version 1.0.2)) を書き漏らしていたので追記した。この記事の内容は各アセットの最新版には当てはまらない可能性がある。 (2014-09-25)

下からすり抜けて上に乗ることができる床の実装例 (Unity C#)

すり抜け床の基本的な考え方は参考資料 1 のサイトを参考にした。角度で切り分けるのが面倒だったので y 座標でざっくり切り分けることにした。

KS コードは以下。すり抜け床にしたいゲームオブジェクトにアタッチして使う。


using UnityEngine;
using System.Collections;

public class ColliderController : MonoBehaviour
{
  Transform playerTransform;

  void Start ()
  {
    playerTransform = GameObject.FindGameObjectWithTag ("Player").transform;
  }

  void FixedUpdate ()
  {
    float f = playerTransform.position.y - thisCollider.bounds.max.y;
    if (f <= 0) {      
      collider2D.enabled = false;
    } else {
      collider2D.enabled = true;
    }
  }
}
  1. 株式会社スマイルブーム. "「すり抜け床」を考えてみる". スマイルブーム.com http://smileboom.com/blog/tkool/throughfloor.html, (参照 2014-09-02).

2014年9月2日火曜日

ブログはじめました

今時冷やし中華じゃあるまいし、そのタイトルはなかろうというものだが、ゲーム関連の話を書いていく所存、デモンズブレイゾン。

Blogger のシステムでつらつら書いたらソースが KS 過ぎてものすごくアレだが、その辺はなるべく気にしない方向で。