テラリウム徹底攻略ガイド(改訂版)

草食動物を作成する(2)

泉 祐介+デジタルアドバンテージ
2003/05/10
Page1 Page2 Page3 Page4 Page5 Page6 Page7

食料となる生物を判定するIsFood

 草食動物の食料は植物であるが、FoodChunksが残りわずかな植物はすぐにでも枯死するかもしれないので、 それは食料と見なさないことにした。また、すでに生殖活動ができなくなった場合は食料には見向きもしないことにした。わざと腹をすかせてほかの動物を攻撃し、邪魔に専念しようというわけだ。

 コードは次のとおりで、いたってシンプルである。

protected override bool IsFood(OrganismState target) {
  return
    !IsRetire &&
    target.FoodChunks > plantChunks4 &&
    target is PlantState;
}
食料となる生物を判定するIsFoodメソッド

 FoodChunksは、その生物を「全部」食べたときの栄養価を表すプロパティである。栄養価の値はint型の値になっている。このプロパティは栄養価と同時に植物の成長具合も表しており、植物が成長しているほど栄養価も大きくなるようになっている。

 plantChunks4はTabataクラスで用意した定数値で、栄養価がこの値にも満たない場合は食料と見なさないことにしている。

脅威となる生物を判定するIsThreat

 基本的には、戦いを仕掛けられたら負けそうな動物が脅威である。具体的な考えはこのような感じだ。

  • 相手が死んでいるときは脅威とは見なさない。
  • 自分と同じ種の動物であれば、先のIsBruntのコードに従って攻撃をしかけてこない。そのため、自分と同じ種の動物は脅威とは見なさない。
  • 相手が攻撃可能であるかどうかも考慮に入れる。相手が肉食動物であればいつでもほかの動物を攻撃できるが、草食動物の場合は空腹、あるいは飢餓状態のときでなければ攻撃できない。攻撃できない動物が近づいたとしても、攻撃されることがないわけだから、その動物は脅威であるとは見なす必要はない。
  • 相手との距離が十分にある場合も脅威とは見なさない。

 後は、実際に戦ったときに勝てるかどうかを判定するために、先に述べたCanWinメソッドを使う。結局、このメソッドはこんな感じになった。

protected override bool IsThreat(OrganismState target) {
  AnimalState a = target as AnimalState;
  if(IsRetire) return false;
  return
    a != null &&
    a.IsAlive &&
    !IsMySpecies(a) &&
    (a.AnimalSpecies.IsCarnivore ||
     a.EnergyState == EnergyState.Hungry ||
     a.EnergyState == EnergyState.Deterioration) &&
    !CanWin(a) &&
    DistanceTo(a) < distToBeThreat;
}
脅威となる生物を判定するIsThreatメソッド

 IsAliveとIsMySpeciesについては、IsBruntメソッドの項で説明したとおりだ。AnimalSpeciesはSpeciesと同様に種に関する情報を表すプロパティであるが、こちらはIAnimalSpeciesインターフェイスを実装したオブジェクトになっており、動物に特化した情報も含まれる。

 IsCarnivoreはそのIAnimalSpeciesインターフェイスのプロパティで、肉食動物であればtrueになる。

 EnergyStateは、その生物の腹具合(より正確には、持っているエネルギーの状態)を表すプロパティで、次のいずれかの値をとる。

  • EnergyState.Dead(餓死)
  • EnergyState.Deterioration(飢餓状態)
  • EnergyState.Hungry(空腹)
  • EnergyState.Normal(通常の状態)
  • EnergyState.Full(満腹)

 ただし、EnergyState.Deadはすでに生物が飢餓によって死んでしまった場合に設定される値で、生物が生きている間はこの値は設定されない。

 最後のdistToBeThreatは、どのくらい敵が近づいたら脅威と見なすかを表す数値で、distToBeBruntなどと同様に、このクラスで用意したフィールドである。

動物の行動を記述するIdleEvent

 いよいよ動物の核心であるIdleEventメソッドの実装だ。動物は1ターンの間に攻撃、防御、摂食、移動を「それぞれ」1回ずつ指定することができる。また、条件が整えば、生殖活動をこれらの行動と同時に行うことができるが、生殖活動についてはKAnimalクラスの方ですでに記述してあるので、特に考える必要はない。従って、実際にプログラムを組むときは、攻撃、防御、摂食、移動という行動に分割して考えると組みやすくなるだろう。

 ということで、まず、攻撃、防御、摂食の方針から説明しよう。ここで示す方針はとても単純だ。

  • 脅威となる生物がいれば、その生物に対して防御する。
  • 攻撃をしかける生物がいれば、その生物を攻撃する。
  • 食料となる生物があれば、その生物を食べる。

 一方、移動は動物の行動の中でも最も重要な位置を占めるため、少しややこしくなる。Tabataでは次のようにした。

  • 脅威となる動物がいるときは、その動物から遠ざかるようにする。
  • 脅威となる動物がいない場合で、攻撃対象となる動物がいるときはその動物に近づいて、攻撃を仕掛けられるようにする。
  • 攻撃を仕掛けるような動物がいない場合で、食料となる生物がいるときはその食料に近づいて、摂食できるようにする。また、攻撃を仕掛けられるような動物がいる場合であっても、自分が成熟しきっていないときなど、攻撃よりも摂食を優先した方がいい場合もあるので、そのときは食料の方に近づくようにする(どのような場合に摂食を優先するかについてはあとで考える)。
  • 上記のいずれにも当てはまらない場合は、食料を求めてさまよい歩くことにする。

 以上の方針をコードの形にすると次のようになる。

protected override void IdleEvent(object sender, IdleEventArgs e) {
  try {
    BeginDefending(Threat);
    BeginAttacking(Brunt);
    if(WannaEat(Food)) BeginEating(Food);

    if(Threat != null) {
      RunAway(Threat);
    } else if(Brunt != null && !IsRequireFood) {
      if(!WithinAttackingRange(Brunt)) {
        BeginMoving(Brunt);
      }
    } else if(Food != null) {
      if(!WithinEatingRange(Food)) {
        BeginMoving(Food);
      }
    } else {
      Wander();
    }
  } catch(Exception exc) {
    WriteTrace(exc.ToString());
  }
}
Tabataの行動パターンを記述したIdleEventメソッド

 まず、このメソッドはイベント・ハンドラとなっているため、全体を“try〜catch”ブロックでくくってある。こうしておかないと、万が一例外が生じたときにその動物が即死してしまうことは前回述べたとおりだ。

 最初にも少し触れたが、ここで鍵となるのはBrunt、Food、Threatである。これらはKAnimalクラスで定義されているプロパティだ。例えば、Foodは食料が見つかればその生物を表すようになり、見つかれなければnullになる。また、複数見つかった場合は最も近くにある生物が格納されるようになっている。同様にして、Bruntには攻撃対象の生物が、Threatには脅威となる生物がそれぞれ格納される。

 さて、try〜catchブロックの最初の3行が防御、攻撃、摂食に関する記述になっている。BeginAttacking、BeginDefending、BeginEatingはAnimalクラスのメソッドであることは前回解説したとおりだが、KAnimalクラスではこれらのメソッドをオーバーライドし、メソッドの内部で引数のチェックを行うようにした。引数のチェックを呼び出し側でいちいち行うようにすると、コードが読みにくくなってしまうからだ。そのため、このメソッドの内部では、引数がnullでないか、あるいは引数として指定した生物が攻撃(あるいは摂食)可能な範囲にいるか、などといったことを確認せずに呼び出すことができる。また、WannaEatは指定した食料を食べるかどうかを決めるメソッドであるが、これについてはあとで述べることにする。

 それ以降の記述はすべて移動に関するものである。RunAwayは指定した生物からの逃避をはじめるメソッドで、KAnimalクラスで定義している。IsRequireFoodは攻撃よりも摂食を優先するかどうかを決めるプロパティで、このクラスで定義したものだ(IsRequireFoodメソッドについてはすぐあとで述べる)。WithinAttackingRange、WithinEatingRangeはそれぞれ指定した生物が攻撃可能な範囲、摂食可能な範囲であるかどうかを調べるAnimalクラスのメソッドである。

 BeginMovingは移動を開始するメソッドであるが、ここではKAnimalクラスでオーバーロードしたものを使っている。ここで使っているのは、引数としてMovementVectorオブジェクトの代わりにOrganismState(生物)を指定するもので、その生物のある(いる)場所に速さFavSpeedで移動する。FavSpeedは速さを指定しなかったときに移動の速さを決定するプロパティで、これはKAnimalクラスで定義している。なお、ここでは生物を指定するメソッドだけを使ったが、これ以外にもKAnimalでは、目的地だけを指定するメソッドや、目的地と速さを直接指定するメソッドを、オーバーロードにより定義している。詳しくはあとで述べる。

 最後に呼び出しているWanderは、食料などを求めてほかのところに移動するメソッドで、これもKAnimalクラスで定義したメソッドだ(これについても後述)。


 INDEX
  テラリウム徹底攻略ガイド(改訂版)
  第4回 KAnimalクラスを使った草食動物の作成
     KAnimalクラスとは
     草食動物を作成する(1)
   草食動物を作成する(2)
     草食動物を作成する(3)
     KAnimalクラスのプロパティとメソッド
     KAnimalクラスで処理しているイベント(1)
     KAnimalクラスで処理しているイベント(2)
 
インデックス・ページヘ  「テラリウム徹底攻略ガイド(改訂版)」


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 記事ランキング

本日 月間