第14回:ボスを作る(3)
ボスに動きを持たせます。ワンパターンな動きではなく、ボスの残り耐久度に合わせて、いくつかの動きや攻撃方法を行わせるようにしましょう。今回、CBoss::Exec()が呼び出されるたびにボスがどのようなアクションをとるかの定義はenumにて行っています。enum BossStatus
{
boss_pattern1, // 第一形態
boss_pattern2, // 第二形態
boss_pattern3, // 第三(最終)形態
boss_explosion // 爆発状態
};
class CBoss : public CEnemyBase
{
/* 省略 */
private:
BossStatus status;
int frame;
void Action1(); // 第一形態時のアクションを実行
void Action2(); // 第二形態時のアクションを実行
void Action3(); // 第三形態時のアクションを実行
bool ActionDestroy(); // 爆発時のアクションを実行
// (戻り値trueでアクションが終了したことを伝える)
};
CBoss::CBoss(void)
{
status = boss_pattern1;
/* 省略 */
}
void CBoss::Exec()
{
// 残り耐久度に合わせアクションを変更
switch(status){
case boss_pattern1: Action1(); break;
case boss_pattern2: Action2(); break;
case boss_pattern3: Action3(); break;
default:
if(ActionDestroy() == true){
// 完了フラグが返されたら自身を消去
RemoveObject(this);
return;
}
}
/* 省略 */
}
void CBoss::Action1()
{
// 真下に移動で静止
if(y < 80.0f) y += 2.0f;
// 砲台をプレーヤーに向ける
bangle[0] = -atan2f(
player->GetPosX() - (x - BUTTERY_X),
player->GetPosY() - (y + BUTTERY_Y));
bangle[1] = -atan2f(
player->GetPosX() - (x + BUTTERY_X),
player->GetPosY() - (y + BUTTERY_Y));
frame++;
if(frame == 80){
ShootFromNozzle(1.0f);
frame = 0;
}
}
class CBoss : public CEnemyBase
{
private:
// 移動に関する情報を格納する変数
int moveflag1, moveflag2;
float mx, my;
void MiniExplosion();
public:
virtual void Damaged();
};
void CBoss::Damaged()
{
hardness--;
// 耐久力が最大時の3分の2になれば次に移る
if(status == boss_pattern1 && hardness <= BOSS_HARDNESS * 2 / 3){
// 小規模の爆発で状態遷移をユーザーにアピール
MiniExplosion();
// データのリセット
frame = 0;
moveflag1 = 0;
bangle[0] = bangle[1] = 0.0f;
status = boss_pattern2;
}
}
void CBoss::MiniExplosion()
{
// 大小からなる30個の爆炎を拡散させる
float xx, yy, a, s;
for(int i = 0; i < 30; i++){
xx = x + (float)(rand() % 300 - 150);
yy = y + (float)(rand() % 100 - 50);
a = atan2f(x - xx, yy - y);
s = 2.0f + 4.0f * (float)rand() / (float)RAND_MAX;
AppendObject(new CExplosion(xx, yy,
2.0f, a, s), EXPLOSION_PRIORITY, true);
}
}
void CBoss::Action2()
{
// 左右に移動
if(moveflag1 == 0){
x -= 0.5f;
if(x < 100.0f) moveflag1 = 1;
}else{
x += 0.5f;
if(x > 540.0f) moveflag1 = 0;
}
frame++;
if(frame % 40 == 0){
ShootFromNozzle(2.0f);
}
if(frame == 240){
// 中央部分から拡散弾
float range[] = {30.0f, 60.0f, 90.0f, 120.0f, 150.0f};
for(int i = 0; i < 5; i++){
AppendObject(
new CEnemyBullet(x, y + 50.0f, d2r(range[i]), 3.0f),
ENEMYBULLET_PRIORITY, true);
}
frame = 0;
}
}
配列内の数値を変えるだけでも、無数のバリエーションを持つ往復運動が行えるので、いろいろ数値を変更してみてください。
void CBoss::Action3()
{
// W文字型に往復運動を行う
const float movedata[][2] =
{
{120.0f, 80.0f},
{220.0f, 180.0f},
{320.0f, 80.0f},
{420.0f, 180.0f},
{520.0f, 80.0f},
};
x += mx;
y += my;
// 座標は浮動小数点のため、誤差を考慮している
if(x > movedata[moveflag1][0] - 0.5f
&& x < movedata[moveflag1][0] + 0.5f
&& y > movedata[moveflag1][1] - 0.5f
&& y < movedata[moveflag1][1] + 0.5f)
{
// 目標位置に到着
// 誤差をなくす
x = movedata[moveflag1][0];
y = movedata[moveflag1][1];
// 次の目標位置への移動量を計算
if(moveflag2 == 0){
mx = movedata[moveflag1 + 1][0] - movedata[moveflag1][0];
my = movedata[moveflag1 + 1][1] - movedata[moveflag1][1];
moveflag1++;
if(moveflag1 == 4) moveflag2 = 1;
}else{
mx = movedata[moveflag1 - 1][0] - movedata[moveflag1][0];
my = movedata[moveflag1 - 1][1] - movedata[moveflag1][1];
moveflag1--;
if(moveflag1 == 0) moveflag2 = 0;
}
// 速度を調整
mx /= 80.0f;
my /= 80.0f;
}
frame++;
if(frame < 400){
// 徐々に砲台を外側へ移動
bangle[0] = d2r(75.0f * (float)frame / 400.0f);
bangle[1] = d2r(-75.0f * (float)frame / 400.0f);
}else if(frame < 600){
int frame2 = frame - 400;
// 一気に砲台を内側に戻す
bangle[0] = d2r(75.0f - 140.0f * (float)frame2 / 200.0f);
bangle[1] = d2r(-75.0f + 140.0f * (float)frame2 / 200.0f);
// 砲台を戻しながら弾をばらまく
if(frame2 % 15 == 0){
ShootFromNozzle(1.0f);
}
}else if(frame < 1000){
int frame2 = frame - 600;
// 砲台を元の位置へ戻す
bangle[0] = d2r(-65.0f + 65.0f * (float)frame2 / 400.0f);
bangle[1] = d2r(65.0f - 65.0f * (float)frame2 / 400.0f);
}else{
frame = 0;
}
}
bool CBoss::ActionDestroy()
{
frame++;
if(frame < 500){
// 500フレーム目まではボス周辺でランダムに爆炎を発生させる
if(frame % 5 == 0){
float xx = x + (float)(rand() % 500 - 250);
float yy = y + (float)(rand() % 300 - 150);
float sz = 1.0f + 2.0f * (float)rand() / (float)RAND_MAX;
AppendObject(new CExplosion(xx, yy, sz, 0.0f, 0.0f),
EXPLOSION_PRIORITY, true);
}
y += 0.2f;
return false;
}else{
// 500フレーム目で大爆発を起こす
float xx, yy, a, s, z;
for(int i = 0; i < 50; i++){
xx = x + (float)(rand() % 100 - 50);
yy = y + (float)(rand() % 100 - 50);
a = atan2f(x - xx, yy - y);
s = 5.0f + 10.0f * (float)rand() / (float)RAND_MAX;
z = 3.0f + 8.0f * (float)rand() / (float)RAND_MAX;
AppendObject(new CExplosion(xx, yy,
2.0f, a, s), EXPLOSION_PRIORITY, true);
}
return true;
}
}
void CBoss::Damaged()
{
hardness--;
if(status == boss_pattern1 && hardness <= BOSS_HARDNESS * 2 / 3){
/* 省略 */
}else if(status == boss_pattern2 && hardness <= BOSS_HARDNESS / 3){
MiniExplosion();
// 移動フラグのセット
frame = 0;
moveflag1 = 2;
moveflag2 = 0;
mx = -(x - 320.0f) / 50.0f;
my = -(y - 80.0f) / 50.0f;
// 砲台位置のリセット
bangle[0] = bangle[1] = 0.0f;
status = boss_pattern3;
}else if(status == boss_pattern3 && hardness <= 0){
// 共倒れを防ぐため、プレイヤーを無敵状態にする
player->SetNoEnemy(true);
frame = 0;
status = boss_explosion;
}
}