<戻る

STGつくろー!
NO16 自機のロールで見る遷移図の実装方法2


 15章で自機のロールを遷移図で作成するさわりをやりました。この章ではその続きを実装していきます。まず、遷移図をおさらいです。


 前回は通常状態ニュートラルと、通常状態入力の関係を実装しました。この章では、通常状態入力とホールド状態の関係をまずは実装していきます。



@ まずはホールド状態からニュートラルへ

 なにはともあれ、ホールド状態から実装していきます。まず、ToDoリストです。

ToDoリスト
・ 左ホールド状態は登録された20度を保持。
・ 右ホールド状態は登録された-20度を保持。
・ 左ホールド状態が0.5秒(30単位)続いても20度を保持。
・ 左ホールド状態が0.5秒(30単位)続いた後にニュートラルになると、通常状態ニュートラルに移行。20度-3度=17度になる。
・ 右ホールド状態が0.5秒(30単位)続いた後にニュートラルになると、通常状態ニュートラルに移行。-20度+3度=17度になる。
・ 左ホールド状態が0.5秒(30単位)続いた後に右に入れると、通常状態入力に移行。20度-2/3度=19.333度になる。

 仕様ではホールド状態が1秒以上続くと急旋回入力状態になりますが、ここは今は考えないことにします。多くのテストコードはとても簡単なので5段目と6段目を示します。

CRollStrategyTest.h
void testHoldRight_NormNeut()
{
   // 右ホールド状態が0.5秒(30単位)続いた後に
   // ニュートラルになると、通常状態ニュートラルに移行。
   // -20度+3度=-17度になる。

   sp<CRollHold> spHold( new CRollHold(-20) );
   sp<CRollNode> spRollNode = spHold;

   // 右30単位ホールド
   for(int i=0; i<30; i++)
      spRollNode->Roll(ROLL_RIGHT, spRollNode);

   // ニュートラル1単位
   spRollNode->Roll(ROLL_NEUTRAL, spRollNode);

   // 角度テスト
   // -20度+3度=-17度
   assertEquals(-17.0, spRollNode->GetAngle() );
}


void testHoldLeft_NormRight()
{
   // 左ホールド状態が0.5秒(30単位)続いた後に
   // 右1単位に切り返すと、通常状態入力に移行。
   // 20度-3度=17度になる。

   sp<CRollHold> spHold( new CRollHold(20) );
   sp<CRollNode> spRollNode = spHold;

   // 左30単位ホールド
   for(int i=0; i<30; i++)
      spRollNode->Roll(ROLL_LEFT, spRollNode);

   // 右1単位
   spRollNode->Roll(ROLL_RIGHT, spRollNode);

   // 角度テスト
   // 20度-3度=17度
   assertEquals(17.0, spRollNode->GetAngle() );
}

 CRollHoldクラスはCRollNodeクラスから派生させます。諸々の小さなコンパイルエラーを修正していくと、Roll関数の戻り値の設定になります。これは、m_dAngle(初期値のまま)を返します。CHoldNodeはコンストラクタで入力された初期値を保持し続けます。

RollHold.cpp
double CRollHold::Roll(int flag, sp<CRollNode> &next)
{
   return m_dAngle;
}

 コンパイルエラーをなくした段階で実行テストを行うと、期待値-17度に対して-20度が返ってきたなどのエラーが返ってきます。次に、このレッドシグナルをグリーンにしましょう。5段目の修正は簡単で、flagがROLL_NEUTRALになったら、nextを通常状態ニュートラルに変更します。とりあえず、その部分だけを下に示します。

RollHold.cpp
double CRollHold::Roll(int flag, sp<CRollNode> &next)
{
   switch(flag)
   {
   case ROLL_NEUTRAL:
      next.SetPtr( new CRollNormNeut( m_dAngle ) );
      return next->Roll( flag, next );
   }

   return m_dAngle;
}

これで実行テストを行うと、5段目はグリーンシグナルになります。やっぱりこの方法の実装はわかりやすくて良いです。

 6段目のエラー回避ですが、引数の情報だけから左ホールド状態が続いているのか、右ホールドだったのが左に切り返されたのかは判断できません。ただ、コンストラクタで渡されるホールド角度を見れば、正なら左、負なら右と判断できます。よって、上のコードはさらに次のように発展していきます。

RollHold.cpp
double CRollHold::Roll(int flag, sp<CRollNode> &next)
{
   switch(flag)
   {
   case ROLL_NEUTRAL:
      next.SetPtr( new CRollNormNeut( m_dAngle ) );
      return next->Roll( flag, next );
   break;
   case ROLL_LEFT:
      if(m_dAngle <= 0){
         next.SetPtr( new CRollNormIpt( m_dAngle ) );
         return next->Roll(flag, next);
      }
   break;
   case ROLL_RIGHT:
      if(m_dAngle >= 0){
         next.SetPtr( new CRollNormIpt( m_dAngle ) );
         return next->Roll(flag, next);
      }
   break;
   }

   return m_dAngle;
}

 これで6段目もグリーンシグナルです。



A 通常状態入力からホールドへ

 仕様では、通常状態入力をずっと続けると20度で固定されます。これをホールド状態と呼んでいます。通常状態からホールド状態へのチェンジする部分のToDoリストは次のようになります。

ToDoリスト
・ 左30単位で20度。同時にホールド状態にチェンジ。さらに1単位でも20度。
・ 右30単位で-20度。同時にホールド状態にチェンジ。さらに1単位でも-20度。

 続いてテストプログラム(左回転の場合)です。

CRollStrategyTest.h
void testNormLeft_Hold()
{
   // 左30単位で20度。同時にホールド状態にチェンジ。

   sp<CRollNormIpt> spNormIpt( new CRollNormIpt(0) );
   sp<CRollNode> spRollNode = spNormIpt;

   // 左31単位
   for(int i=0; i<31; i++)
      spRollNode->Roll(ROLL_LEFT, spRollNode);

   // 角度テスト
   // 30単位でホールド状態になっているので、20度
   assertEquals(20.0, spRollNode->GetAngle() );
}

 CRollNormIptオブジェクトを直接生成して関係をテストできるというのは、今回の方法の大きな利点ですよね。条件分岐だとこうはいきません。このテストプログラムはこのまま通りますが、実行テストでエラーになります。期待値は20度ですが、返ってきた角度は20.667度になります。これは、まぁ当然でして、ホールド状態のオブジェクトを生成していないからです。CRollNormIptクラスを改良しましょう。

 CRollNormIpt::Roll関数内で角度をチェックし、ホールド角度に突入したら、ホールドオブジェクトを生成して、振る舞いを移譲します。

RollNormIpt.cpp
#define ROLL_NORMIPTUNITANG   2.0/3.0  // 入力角度
#define ROLL_HOLD 20.0                         // ホールド角度

double CRollNormIpt::Roll(int flag, sp<CRollNode> &next)
{
   switch(flag)
   {
   case ROLL_LEFT:
      if(m_dAngle + ROLL_NORMIPTUNITANG >= ROLL_HOLDANG){
         next.SetPtr( new CRollHold( ROLL_HOLDANG ) );
         return next->Roll(flag, next);
      }
      m_dAngle += ROLL_NORMIPTUNITANG;
      return m_dAngle;
   break;
   case ROLL_RIGHT:
      if(m_dAngle - ROLL_NORMIPTUNITANG <= -ROLL_HOLDANG){
         next.SetPtr( new CRollHold( -ROLL_HOLDANG ) );
         return next->Roll(flag, next);
      }
      m_dAngle -= ROLL_NORMIPTUNITANG;
      return m_dAngle;
   break;
   case ROLL_NEUTRAL:
      next.SetPtr( new CRollNormNeut(next->GetAngle()) );
      m_dAngle = next->Roll(flag, next);
      return m_dAngle;
   break;
   }

   return 0.0;
}

 少し不恰好ですけれども、それほど面倒なことにはなっていません。リファクタリングも可能ですが、今はこのままにしておきます。これでテストはオールグリーンになります。


 ホールドはさらに「急旋回」とのつながりがあります。これは次の章で実装しましょう。