テラリウム徹底攻略ガイド

KAnimalクラスのGetBypassメソッド

大石 晃裕+デジタルアドバンテージ
2002/06/06

障害物を回避するGetBypassメソッド

 次にGetBypassメソッドの説明である。これは前回説明したMoveCompletedEventメソッドの中で使用されている。MoveCompletedEventメソッドについては前回を参照してほしい。

protected Point GetBypass(OrganismState target) {
  if(target == null) {
    return new Point(OrganismRandom.Next());
  }
  int dx = target.GridX - State.GridX;
  int dy = target.GridY - State.GridY;
  int size = State.CellRadius + target.CellRadius + 2;
  if(Math.Abs(dx) > Math.Abs(dy)) {
    if(OrganismRandom.Next(2) == 1) {
      return new Point(Position.X,(target.GridY + size) << 3);
    } else {
      return new Point(Position.X,(target.GridY - size) << 3);
    }
  } else {
    if(OrganismRandom.Next(2) == 1) {
      return new Point((target.GridX + size) << 3,Position.Y);
    } else {
      return new Point((target.GridX - size) << 3,Position.Y);
    }
  }
}
KAnimalクラスで定義されているGetBypassメソッド

 このメソッドを簡単にいえば、何か障害物にぶつかったときにその生物を迂回(バイパス)するための座標を取得する関数だ。戻り値としては迂回するための目標となる座標が返るようになっている。

 まず、前提知識として、テラリウムにおける「グリッド」の概念が必要だ。これは「Terrarium開発ガイド」の「生物が占有するスペースを知るには?」が参考になるが、グリッドとは、テラリウムのフィールドを1辺が8ピクセルの正方形で分割した座標系である。各生物の大きさはその半径で示されるが、実際に生物がフィールド上で占めるスペースはその半径を持った円ではなく、いくつかのグリッドからなる正方形として処理される。

 ここでの実装としては、まず、自分とその迂回する生物のグリッドによる相対関係をdx、dyという変数に格納している。そして迂回する生物と自分の大きさを足して、若干の余裕をもたせたものをsize変数に格納する。

int size = State.CellRadius + target.CellRadius + 2;

 次にdx、dyの絶対値を比較している。これでdxの絶対値の方が大きい場合、自分と迂回する目標は横に並んだ配置となっているということであり、縦に迂回する必要がある。そして上に避けるか下に避けるかの決定については、ランダムに決定している。次の図は上に避けている場合に、このメソッドで使用している各オブジェクトの値と生物が占めるグリッドの関係を図示したものだ。

各オブジェクトの値と生物が占めるグリッドの関係

 この場合、メソッドの戻り値となる座標は次の行によって算出される。

return new Point(Position.X,(target.GridY - size) << 3);

 まずPointのコンストラクタの第1引数には現在のX座標の値Position.Xを渡している。これは単純で、前述したとおりこの部分では縦に避ければよいということが分かっているので、X座標は移動する必要がないからだ。

 問題は次の第2引数である。

(target.GridY - size) << 3

 まず、括弧の中から解説しよう。この計算は現在の自分の位置のグリッドから、先ほど求めたグリッドによる占有サイズ同士の和を減算している。分かりやすくいえば、sizeはどれだけグリッドで移動すればいいかを表すもので、target.GridYは現在の自分のグリッドの場所である。この計算で得られるものは、自分が移動すべきグリッドの場所だ。

 そしてそれを3bit左にシフトしている。この操作によって、グリッド単位で表された座標を通常の(BeginMovingメソッドなどに指定する)座標に変換できる。これは「Terrarium開発ガイド」に従ったものだが、単に8倍するのと同じだ。

リストになったKAnimalの3つのプロパティ

 ところで、冒頭でも述べたように、実は今回使用したKAnimalの実装が少し前回とは変わっている。次に、新たに加えられた機能について解説しておく。

 今回、IsFoodメソッドやIsThreatメソッド、IsBruntメソッドにおいてtrueが返されるような生物が、リストとして取得できるようになった。前回の実装では、1つの生物しか取得できなかったのだが(Foodプロパティなど)、FoodList、ThreatList、BruntListというプロパティによって、このリストを取得できるようにした。

 このようにしたことによる利点でまず思いつくのは、例えばThreatListである。現在のKomagomeの実装では、敵から逃げるときにはさみうちにあった場合、簡単に追い詰められてしまう。しかしリストで取得できるようになったことによって、複数の敵から効率よく逃げるルーチンを組むことも可能になる。またFoodListでは、より体力の回復する食料を選ぶようにする、BruntListでは、最も弱っている敵に襲い掛かるなど、かなり柔軟な実装が可能になる。

 この機能に付随したものとして、この取得できるリストの生物の並び替え方も指定できるようにした。前回では自動的に最も近い生物を取得するようになっていたのだが、今回、その比較部分を分離し、ICompareインターフェイスを実装したオブジェクトへの参照をそれぞれFoodComp、ThreatComp、BruntCompというプロパティに格納し利用している。KAnimalクラスを利用するプログラムで比較のためのルーチンを設定したい場合には、これを置き換えればよい(ICompareインターフェイスについては.NET Framework SDKのヘルプを参照のこと)。

 また、もちろん下位互換としてFood、Threat、Bruntのプロパティも実装している(Komagomeではまだこちらを利用している)。では、説明はこれぐらいにして、その実装を見てみよう。

protected void UpdateTarget() {
  foodList.Clear();
  threatList.Clear();
  bruntList.Clear();

  foreach(OrganismState target in foundCreatures) {
    OrganismState t = LookFor(target);
    if(t == null) continue;
    if(IsFood(t)) {
      foodList.Add(t);
    }
    if(IsThreat(t)) {
      threatList.Add(t);
    }
    if(IsBrunt(t)) {
      bruntList.Add(t);
    }
  }
  foodList.Sort(foodComp);
  bruntList.Sort(bruntComp);
  threatList.Sort(threatComp);
}
前回から修正したKAnimalクラスのUpdateTargetメソッド

 このコード、前回で説明したUpdateTargetメソッドなのだが、先ほど説明した機能を実現するためにここが大きく変更されている。

 実装を詳細に見てみると、まず始めにfoodList、threatList、bruntListをクリアしている。そのあとで、foreachステートメントによって、それぞれのlistについてIsFood、IsTreat、IsBruntがtrueを返すものを追加している。そして最後に、セットされた要素を用意されたICompareインターフェイスを使いソートしている。

 ここで少し、参考として、ICompareインターフェイスの実例としてデフォルトで使用されるものを見てみよう。

 デフォルトでKAnimalが利用するICompareインターフェイスを実装したクラスは次のようになっている。実装の概要としては距離の近い順番に並べるためのクラスとなっている。

public class DistComparer : IComparer {
  Animal parent;
  public DistComparer(Animal parent) {
    this.parent = parent;
  }
  public int Compare(object a,object b) {
    double da = parent.DistanceTo(a as OrganismState);
    double db = parent.DistanceTo(b as OrganismState);
    return Math.Sign(da - db);
  }
}
生物を距離の近い順番に並べるための、ICompareインターフェイスを実装したクラス

 メンバとしてAnimalクラスのparentという変数を持っているが、これはDistanceToメソッドを利用するために必要だ。この変数はコンストラクタによって代入されるようになっている。

 さて、最も重要な部分、比較するメソッドであるCompareの説明をしよう。

double da = parent.DistanceTo(a as OrganismState);
double db = parent.DistanceTo(b as OrganismState);

 このコードによって、比較される2つの生物の自分からの距離をまず求めている。

return Math.Sign(da - db);

 そしてこのコードによって戻り値を作成している。この戻り値は「da < db」であった場合は−1が返り、「da == db」であった場合は0、それ以外の場合、つまり「da > db」である場合に1が返るようになっている。

 さて、話を戻そう。ここで作成されたfoodList、threatList、bruntListは、それぞれ先ほど説明した新しく用意されたプロパティである、FoodList、ThreatList、BruntListを利用してReadOnlyなArrayListとして取得できるようになっている。

 さて、上記が大きな変更点だが、細かい変更も行われている。以前からあったWannaEat、WannaAttack、WannaDefendという仮想メソッドのほかにWannaReproduceが加えられた。これにより、どれぐらい繁殖するか、ということも制御できるようになった。これは体力が少ないために繁殖を抑制するなどに使用できる。

 最終回となる次回は、障害物を回避するためのより最適な経路探索ルーチンと、ダンスによる動物間通信のためのルーチンを作成する予定だ。End of Article

 

 INDEX
  [連載]テラリウム徹底攻略ガイド
  第5回 KAnimalクラスを使った肉食動物の作成
     肉食動物を作成する
     肉食動物の行動パターン
     KAnimalクラスのBeginChaseメソッド
   KAnimalクラスのGetBypassメソッド
 
インデックス・ページヘ  「連載  テラリウム徹底攻略ガイド」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間