STGつくろー!
NO14 自機のテスト見直し(弾発射とパワーアップ)
自機が弾を発射する部分、およびパワーアップする部分の仕様を抜き出して見ます。
自機仕様 |
・ ショットボタンを押すと、通常弾とサブウェポンを発射します。 ・ ボムボタンを押すとボムを発射します。 ・ 通常弾とサブウェポンはパワーアップアイテムにより5段階にパワーアップします。ボムは変わりません。 |
この部分から考えられるToDoリストは次のようになります。
ToDoリスト ショット ・ ショットボタンを1回押して、通常弾とサブウェポン取得 ・ ショットボタンを2回押して、通常弾とサブウェポン2つずつ取得 ・ ボムボタンを1回押して、ボムを取得 ・ ボムボタンを2回押して、ボムを2つ取得 パワーアップ ・ パワーアップアイテムを1個取って、通常弾とサブウェポンのパワー1段階アップ ・ パワーアップアイテムを5個取って、通常弾とサブウェポンのパワー5段階アップ ・ パワーアップアイテムを6個取って、通常弾とサブウェポンのパワー5段階アップ
@ 弾生成のテスト
ショットボタン、ボムボタンに対応する関数、パワーアップアイテムを取った事によってプレーヤーの状態を変える関数が必要になりますね。ショットに関するテストコードは次のようになります。
PlayerTest.h void testShootNormalBulletAndSubWeapon(){
// 通常弾とサブウェポンを生成するテスト
sp<CPlayer> spPlayer(new CPlayer);
list<sp<CBullet> > lst;
// プレーヤーオブジェクトに弾リストを渡す
int Norm = spPlayer->Shoot(lst);
int SW = spPlayer->ShootSubWeapon(lst);
int Bomb = spPlayer->ShootBomb(lst);
// 生成物のチェック
assertEquals(1, Norm);
assertEquals(1, SW);
assertEquals(1, Bomb);
assertEquals(3, lst.size());
}
プレーヤーは3つの弾生成関数(Shoot関数、ShootSubWeapon関数、ShootBomb関数)を持つことにしました。引数にはCBulletオブジェクトのスマートポインタのリストを渡します。リストにするのは、1回の呼び出しで複数の弾を同時に生成する事があるからです。関数の戻り値は新しく生成した弾の数を返します。テストでは、返された弾の数をチェックしています。
ショットボタンを2回押した時のテストを次に作成します。これは上のテストプログラムを2回繰り返せば問題ないでしょう。
PlayerTest.h void testShootNormalBulletAndSubWeapon(){
// 通常弾とサブウェポンを生成するテスト
sp<CPlayer> spPlayer(new CPlayer);
list<sp<CBullet> > lst;
for(int i=1; i<2; i++){
// プレーヤーオブジェクトに弾リストを渡す
int Norm = spPlayer->Shoot(lst);
int SW = spPlayer->ShootSubWeapon(lst);
int Bomb = spPlayer->ShootBomb(lst);
// 生成物のチェック
assertEquals(1*i, Norm);
assertEquals(1*i, SW);
assertEquals(1*i, Bomb);
assertEquals(3*i, lst.size());
}
}
このテストを通るようにCPlayerクラスに関数を追加します。テストプログラム内で3つの弾クラス(CNormalBullet、CSubWeapon、CBomb)が新しく定義されています。これらはCBulletクラスの派生クラスです。まだどのような弾になるかは決めていませんので、とりあえずクラスだけ定義しておきます。CPlayerクラスのメンバ関数は次のように内部で弾を生成してリストとして返す実装しました。
Player.cpp int CPlayer::Shoot(list<sp<CBullet> > &bul){
// 通常弾を生成
sp<CBullet> tmp(new CNormalBullet);
bul.push_back(tmp);
return 1;
}
int CPlayer::ShootSubWeapon(list<sp<CBullet> > &bul){
// サブウェポンを生成
sp<CBullet> tmp(new CSubWeapon);
bul.push_back(tmp);
return 1;
}
int CPlayer::ShootBomb(list<sp<CBullet> > &bul){
// ボムを生成
sp<CBullet> tmp(new CBomb);
bul.push_back(tmp);
return 1;
}
テストはこれで通ります。
A パワーアップのテスト
パワーアップは通常弾とサブウェポンにだけに作用する仕様にしています。パワーアップアイテムを取った事を表すCPlayer::PowerUp関数、および現在のパワーアップの情報を取得するCPlayer::GetPowerUpLevel関数を設けます。また、弾クラスにもパワーアップを設定取得できるようにしておきます。
PlayerTest.h void testPowerUp_LEVEL2()
{
sp<CPlayer> spPlayer(new CPlayer);
// 1段階パワーアップ
spPlayer->PowerUp();
assertEquals("testPowerUp_LEVEL2", 2, spPlayer->GetPowerUpLevel());
// 弾もチェック
list<sp<CBullet> > lst;
list<sp<CBullet> >::iterator it;
spPlayer->Shoot(lst);
spPlayer->ShootSubWeapon(lst);
spPlayer->ShootBomb(lst);
it = lst.begin();
assertEquals("testPowerUp_LEVEL2", 2, (*it++)->GetPowerUpLevel() );
assertEquals("testPowerUp_LEVEL2", 2, (*it++)->GetPowerUpLevel() );
assertEquals("testPowerUp_LEVEL2", 1, (*it)->GetPowerUpLevel() );
}
CPlayer::PowerUp関数の実装は次のとおりです。
Player.cpp void CPlayer::PowerUp()
{
m_iPowerUpLevel++;
}
非常に簡単ですね。次に弾クラスに設定関数を設けます。
CBullet.cpp CBullet::CBullet()
{
m_iPowerUpLevel = 1
}
void CBullet::SetPowerUp(unsgined int power)
{
m_iPowerUpLevel = power;
}
int CBullet::GetPowerUpLevel()
{
return m_iPowerUpLevel;
}
これも問題ないでしょう。この段階でコンパイルは通りますが、実行テストはレッドシグナルです。弾レベルは2になってほしいのに、戻ってきたのが1だからです。これは、CPlayerの弾生成部分に変更が必要になります。
Player.cpp list<sp<CBullet> > CPlayer::Shoot(){
// 通常弾を生成
sp<CBullet> tmp(new CNormalBullet);
tmp->SetPowerUpLevel(m_iPowerUpLevel);
return tmp;
}
list<sp<CBullet> > CPlayer::ShootSubWeapon(){
// サブウェポンを生成
sp<CBullet> tmp(new CSubweapon);
tmp->SetPowerUpLevel(m_iPowerUpLevel);
return tmp;
}
list<sp<CBullet> > CPlayer::ShootBomb(){
// ボムを生成(ボムはパワーアップしない)
sp<CBullet> tmp(new CBomb);
return tmp;
}
これで、グリーンシグナルです。
B パワーアップ5段階目
パワーアップアイテムを5個取った状態をテストします。仕様ではこれが最高レベルです。
PlayerTest.h void testPowerUp_LEVEL6()
{
sp<CPlayer> spPlayer(new CPlayer);
// 5段階パワーアップ
for(int i=0; i<5; i++)
spPlayer->PowerUp();
assertEquals( 6, spPlayer->GetPowerUpLevel());
// 弾もチェック
list<sp<CBullet> > lst;
list<sp<CBullet> >::iterator it;
spPlayer->Shoot(lst);
spPlayer->ShootSubWeapon(lst);
spPlayer->ShootBomb(lst);
it = lst.begin();
assertEquals( 6, (*it++)->GetPowerUpLevel() );
assertEquals( 6, (*it++)->GetPowerUpLevel() );
assertEquals( 1, (*it)->GetPowerUpLevel() );
}
テストはこれでOK。そして、これまでの実装のままでグリーンシグナルになります。こういうテストも重要です。
C パワーアップ6段階目
パワーアップアイテムを6個取った状態のテストです。仕様では、6段階目のパワーアップは無効です。テストプログラムはBとほぼ同じです。
PlayerTest.h void testPowerUp_MoreLevel()
{
sp<CPlayer> spPlayer(new CPlayer);
// 6段階パワーアップ
for(int i=0; i<6; i++)
spPlayer->PowerUp();
assertEquals( 6, spPlayer->GetPowerUpLevel());
// 弾もチェック
list<sp<CBullet> > lst;
list<sp<CBullet> >::iterator it;
spPlayer->Shoot(lst);
spPlayer->ShootSubWeapon(lst);
spPlayer->ShootBomb(lst);
it = lst.begin();
assertEquals( 6, (*it++)->GetPowerUpLevel() );
assertEquals( 6, (*it++)->GetPowerUpLevel() );
assertEquals( 1, (*it)->GetPowerUpLevel() );
}
このテストは残念ながらこれまでの実装では実行エラーとなります。レベル6を期待したのですが、レベル7が返ってきます。これは自機のパワーアップに上限を設けることで解決します。実装の変更は次のとおりです。
Player.cpp #define PLAYER_MAXLEVEL 6
void CPlayer::PowerUp()
{
if(m_iPowerUpLevel < PLAYER_MAXLEVEL)
m_iPowerUpLevel++;
}
割と簡単です。最高レベルはマクロ定数で定義しておきました。
前章と合わせて、これで弾を発射してレベルアップする自機が出来ました。しかもテストも終わっています。ここまでの部分でリファクタリングするべき部分はありません。次の章からは実はめちゃくちゃ面倒な部分に手を付けます。昨今の縦STGは自機を左右に動かすと実際の戦闘機がそうするように左右に傾きます。いわゆる「ロール」です。今回のSTGでもこのロールを採用します。この動きは非常に細かな仕様にしているために、テストが膨大になります。どういう仕様でどういうテストをするか?次章でじっくり検討しましょう。