アマゾンバナーリンク

ディスプレイ広告

スポンサーリンク

凍符「パーフェクトフリーズ」

2020年1月16日

今回の弾幕は、東方紅魔郷 お馴染みのチルノスペルカードの凍符「パーフェクトフリーズ」です。

それとボスの移動と弾幕の制御部分を切り分けました。

記事内広告

ボスの弾幕生成クラス

CBossBulletFactory.csというファイルを新規作成して以下のコードを追加。

public delegate int MOVE_BOSS_POS(double x1, double y1, double x2, double y2, double dist, int t);
public class CBossBulletFactory
{
protected MOVE_BOSS_POS MoveBossPos;
protected int Cnt = 0;
protected double BaseAngle = 0.0;
public CBossBulletFactory(MOVE_BOSS_POS move_boss_pos)
{
MoveBossPos = move_boss_pos;
}
public virtual void Run(double x, double y) { }
}

基底クラスであるCBossBulletFactoryを派生したクラスが弾幕の制御をするクラスです。
Run関数を呼ぶことによって弾幕を生成します。
ただ弾幕の計算のとボスの移動も制御しなければならないので、デリゲートでMOVE_BOSS_POSを宣言し、弾幕クラスを生成する時にMoveBossPos関数を受け取ります。
これによってボスを制御するクラスは非常に見やすくなりました。

public class CBoss : CMover
{
public bool DeathFlag { set; get; } = false;
public bool MutekiFlag{ set; get;} = false;
protected CPhysics Phy = new CPhysics();
public CBoss(double x, double y) : base(x, y,1, -5.0, -5.0, 5.0, 5.0)
{
}
public virtual void Move() { }
public virtual void Draw() { }
//今いる位置からdist離れた位置にtカウントで移動する
public int MoveBossPos(
double x1, double y1, double x2, double y2, double dist, int t)
{
int i = 0;
double x, y, angle;
for (i = 0; i < 1000; i++)
{
x = X;//今のボスの位置をセット
y = Y;
angle = SGP.Rang(PI);//適当に向かう方向を決める
x += Math.Cos(angle) * dist;//そちらに移動させる
y += Math.Sin(angle) * dist;
if (x1 <= x && x <= x2 && y1 <= y && y <= y2)
{
//その点が移動可能範囲なら
//input_phy_pos(x, y, t);
// x1 初期X座標 y1 初期Y座標 x2 目標X座標 y2 目標Y座標かける時間
if (Phy != null)
Phy.InputPhyPos(X, Y, x, y, t);
return 0;
}
}
return -1;//1000回試してダメならエラー
}
}
public class CBoss1 : CBoss
{
CBossBulletFactory BossBulletFactory;
int MaxLife = 650;
int State = 0; 
public CBoss1(double x, double y) : base(x, y)
{ }
public override void Move()
{
switch (State)
{
case 0: // 移動登録
Life = 1;
MutekiFlag = true;
if (Phy != null)
{
Phy.InputPhyPos(X, Y, 240, 170, 90);
State = 1;
}
break;
case 1: // 移動
if (!Phy.Flag)
{
Cnt = 0;
State = 2;
}
break;
case 2:
Life += 4;
if (Life >= MaxLife)
{
Life = MaxLife;
MutekiFlag = false;
State = 3;
BossBulletFactory = new CPerfectFreeze(MoveBossPos);
}
break;
case 3:
BossBulletFactory.Run(X, Y);
break;
}
}
}

弾幕を74行目で生成し、78行目で計算してます。別の種類の弾幕にしたい時は74行目を入れ替えるだけで済む様になりました。非常に簡単ですよね。この様にクラスを細かく設計してやると、余計な変数に触らなくてよくなるので、バグの可能性が少なくるし、発生したとしても特定しやすくなります。

凍符「パーフェクトフリーズ」

では、実際のパーフェクトフリーズのプログラムを見てみましょう。

public class CPerfectFreeze : CBossBulletFactory
{
public CPerfectFreeze(MOVE_BOSS_POS move_boss_pos) : base(move_boss_pos)
{
}
public override void Run(double x, double y)
{
const int TM002 = 650;
int t = Cnt % TM002;
double angle;
if (t == 0 || t == 210)
{
//40<x<FMX-40 50<y<150 の範囲で100離れた位置に80カウントで移動する
MoveBossPos(40, 50, FIELD_MAX_X - 40, 150, 100, 80);
}
//最初のランダム発射
if (t < 180)
{
//1カウントに2回発射
for (int i = 0; i < 2; i++)
{
SGP.BulletBuf.Add(new CBullet(x: x, y: y,
speed: 3.2 + CUtility.Rang(2.1),
angle: CUtility.Rang(PI2 / 20) + PI2 / 10 * t,
graph_type: 7, color: DX.GetRand(6), state: 0, pi2or1: 1.0, se_flag: t % 10 == 0, kaiten:1));
}
}
//自機依存による8方向発射
if (210 < t && t < 270 && t % 3 == 0)
{
angle = SGP.GetPlayerAnglePI2(x, y);
for (int i = 0; i < 8; i++)
{
SGP.BulletBuf.Add(new CBullet(x: x, y: y,
speed: 3.0 + CUtility.Rang(0.3),
//自機とボスとの成す角を基準に8方向に発射する
angle: angle - PI / 2 * 0.8 + PI * 0.8 / 7 * i + CUtility.Rang(PI / 180),
graph_type: 7, color: 0, state: 2, pi2or1: 1.0, se_flag: t % 10 == 0, kaiten: 1));
}
}
for(int i = 0; i < SGP.Bullet.Count; ++i)
{
//tが190の時に全てストップさせ、白くし、カウントリセット
if (SGP.Bullet[i].State == 0)
{
if(t == 190)
{
SGP.Bullet[i].Kaiten = 0;//弾の回転を止める
SGP.Bullet[i].Speed = 0.0;
SGP.Bullet[i].Color = 0;
SGP.Bullet[i].Cnt = 0;
SGP.Bullet[i].State = 1;
}
}
//ランダムな方向に移動し始める
if (SGP.Bullet[i].State == 1)
{
if (SGP.Bullet[i].Cnt == 200)
{
SGP.Bullet[i].Angle = CUtility.Rang(PI);
SGP.Bullet[i].Kaiten = 1;
}
if (SGP.Bullet[i].Cnt > 200)
SGP.Bullet[i].Speed += 0.01;
}
}
++Cnt;
}
}

この弾幕は1周期650カウントです。アルゴリズムとしては

  • 初めの180カウント未満まで、 全方向ばら撒きショットをうつ
  • 190カウントの時に全てストップさせ、白くし、カウントリセット
  • 200カウントで全方向で再び動き始める

均等にランダムにするための工夫

360°のランダムな方向の生成ならCUtility.Rang(PI);でよいのですが、均等なランダムにならない可能性があります。そこで CUtility.R ang(PI2/20)+PI2/10*tと書いてます。これは

  1. PI2/10*tが1の時は36度を基準に+-PI2/20度つまり+-16度
  2. PI2/10*tが2の時は72度を基準に+-16度
  3. PI2/10*tが3の時は108度を基準に+-16度
  4. PI2/10*tが4の時は144度を基準に+-16度
  5. PI2/10*tが5の時は180度を基準に+-16度
  6. PI2/10*tが6の時は216度を基準に+-16度
  7. PI2/10*tが7の時は252度を基準に+-16度
  8. PI2/10*tが8の時は288度を基準に+-16度
  9. PI2/10*tが9の時は324度を基準に+-16度
  10. PI2/10*tが10の時は360度を基準に+-16度

と10カウント(約1/6秒)かけて1周します。これによってどこか1つの方向に偏らないような乱数を発生させることができます。