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;
}
少し不恰好ですけれども、それほど面倒なことにはなっていません。リファクタリングも可能ですが、今はこのままにしておきます。これでテストはオールグリーンになります。
ホールドはさらに「急旋回」とのつながりがあります。これは次の章で実装しましょう。