アマゾンバナーリンク

ディスプレイ広告

スポンサーリンク

弾幕制作 月符 「サイレントセレナ」と禁忌「恋の迷路」

前回で講座自体は終わりましたがゲームは完成してないので、まだまだ作業は続きます。今回の成果は、ボス用の2D背景と3D背景とボスの弾幕をつくりました。 パチュリー・ノーレッジのスペルカード月符 「サイレントセレナ」フランドール・スカーレットのスペルカード 禁忌「恋の迷路」 です。

記事内広告

月符 「サイレントセレナ」

この弾幕を生成する部分のソースコードは以下の様になります。

// サイレントセレナ
void SilentSerena()
{
const int TM001 = 60;
int t = Cnt % TM001;
int cnt = Cnt % 400;
if (t == 0)
{
if (move_cnt == 1)
MoveBossPos(20, 30, FIELD_MAX_X - 20, 120, 60, 60);
BaseAngle = SGP.GetPlayerAnglePI2(X, Y);//基準と成る角度をセット
}
// 周期の半分で角度を変える
if (t == TM001 / 2 - 1)
{
BaseAngle += PI / 20;
}
// -PIからPIまでの範囲に補正する
//while( BaseAngle < -PI ) BaseAngle += PI2;
//while( BaseAngle >= PI ) BaseAngle -= PI2;
if (t % 6 == 0)
{
for (int i = 0; i < 20; i++)
{
SGP.BulletBuf.Add(new CBullet(x:X, y:Y, angle:BaseAngle + PI2 / 20 * i, speed:2.7, graph_type:8, color:4,pi2or1:1.0));
}
}
if (t % 4 == 0)
{
SGP.BulletBuf.Add(new CBullet(
x:DX.GetRand(FIELD_MAX_X), y:DX.GetRand(200), 
angle:PI / 2, speed:1.0 + SGP.Rang(0.5), graph_type:8, color:0, pi2or1:1.0));
}
if (0 <= cnt && cnt <= 120 && cnt % 240 == 1)
{
for (int s = 0; s <= 150; s++)
{
if (s % 6 < 4) continue;
SGP.BulletBuf.Add(
new CRainbowBullet(
x:X, y:Y, vx:-2.5f + 5.0f / 150 * s,
vy:-8.0, vvy:0.2, graph_type:2, color:(s / 6) % 9, pi2or1:1.0));
}
}
if (t == TM001 - 1)
{
move_cnt = (move_cnt + 1) % 4;
}
}

やってることは簡単で以下の処理を400カウント1周期として繰り返してるだけです。本家では最後の虹の弾幕はなくてオリジナルで付け足しました。

  • 60カウント周期にSGP.GetPlayerAnglePI2でプレイヤーとボスとのなす角を計算
  • 60カウント周期の更に4周期に1回ボスを移動させる
  • 周期の半分でPI/20だけ角度を変える。(180/20なのでおよそ9度)
  • 6カウントに1度、360度に20方向に均等に弾幕を打つ
  • 4カウントに1度、PI/2(真下方向)に向けて雨粒の弾幕を生成
  • この弾幕全体が400カウント周期でその始めの120カウントなおかつ240カウント以内なら虹の弾幕を生成

禁忌「恋の迷路」

この弾幕を生成する部分のソースコードは以下の様になります。

double BaseAngle2 = 0.0;
int tcnt=0, cnt=0, cnum=0;
//恋の迷路
void KokinoMeiro()
{
const int TM003 = 600;
const int DF003 = 20; 
int i, j, k, t = Cnt % TM003, t2 = Cnt;
double angle;
if (t2 == 0)
{//最初なら
//40<x<FMX-40 50<y<150 の範囲で100離れた位置に80カウントで移動する
MoveBossPos(X, Y, FIELD_MAX_X /2, FIELD_MAX_Y/2, 60, 50);
cnum = 0;
}
if (t == 0)
{//1周期の最初なら
BaseAngle = SGP.GetPlayerAnglePI2(X, Y);//基準と成る角度をセット
cnt = 0;
tcnt = 2;
}
if (t < 540 && t % 3 != 0)
{
BaseAngle = SGP.GetPlayerAnglePI2(X, Y);//基準と成る角度をセット
//撃たない方向なら撃たない
if (tcnt - 2 == cnt || tcnt - 1 == cnt)
{
if (tcnt - 1 == cnt)
{
//ベースとなる角度をセット
BaseAngle2 = BaseAngle + PI2 / DF003 * cnt * (cnum!=0 ? -1 : 1) - PI2 / (DF003 * 6) * 3;
tcnt += DF003 - 2;
}
}
//それじゃなければうつ
else
{
for (i = 0; i < 6; i++)
{//1回に6発ずつうつ
angle = BaseAngle + PI2 / DF003 * cnt * (cnum != 0 ? -1 : 1) + PI2 / (DF003 * 6) * i * (cnum != 0 ? -1 : 1);
SGP.BulletBuf.Add(new CBullet(x: X, y: Y, angle: angle, speed: 2, graph_type: 8, color: cnum != 0 ? 1 : 4, pi2or1: 1.0));
}
}
cnt++;
}
//少し大きな弾で円形発射
if (40 < t && t < 540 && t % 30 == 0)
{
for (j = 0; j < 3; j++)
{
angle = BaseAngle2 - PI2 / 36 * 4;
for (i = 0; i < 27; i++)
{
SGP.BulletBuf.Add(new CBullet(x: X, y: Y, angle: angle, speed: 4 - 1.6 / 3 * j, graph_type: 7, color: cnum != 0 ? 6 : 0, pi2or1:
angle -= PI2 / 36;
}
}
}
if (t == TM003 - 1)
cnum++;
}

この弾幕は処理を600カウントを1周期として以下の処理をしています。

  • 周期の一番初めに画面の中央にボスを移動
  • SGP.GetPlayerAnglePI2でプレイヤーとボスのなす角度を計算
  • 回転方向をカウントしていき、1周分-2か1周分-1なら発射しない
  • それ以外は1回に6ずつ弾幕を打つ
  • 大きな弾は、40カウント以上、530カウント未満で30カウントに1回、PI2/36(10度)ずつずしながら27方向に打つのを3回繰り返します。

それと今回からなす角度を計算する仕様を変えました。

private double GetMoverAngle(List<CMover> mover, double x, double y, bool pi2_flag)
{
// 一番近くのmoverの要素を取得する
double min = -1;
int num = 0;
for (int i = 0; i < mover.Count; ++i)
{
double dist = (mover[i].X - x) * (mover[i].X - x) + (mover[i].Y - y) * (mover[i].Y - y);
if (min == -1 || dist < min)
{
min = dist;
num = i;
}
}
return min == -1 ? 0.75 : Math.Atan2(mover[num].Y - y, mover[num].X - x) / (pi2_flag ? PI2 : 1.0);
}
// SGP.GetPlayerAnglePI2を使った時にはCBulletの引数pi2or1に1.0を入れる事
// GetPlayerAngle1を使った時にはCBulletの引数pi2or1にPI2を入れる事
// プレイヤーまでの角度を計算する(0~PI2の間で値が返ってくる)
public double GetPlayerAnglePI2(double x, double y)
{
List<CMover> player = new List<CMover>(Player);
return GetMoverAngle(player, x, y, false);
}
// プレイヤーまでの角度を計算する(0~1.0の間で値が返ってくる)
public double GetPlayerAngle1(double x, double y)
{
List<CMover> player = new List<CMover>(Player);
return GetMoverAngle(player, x, y, true);
}

プレイヤーまでの角度を計算する処理ですが、0~PI2の間で返ってくるGetPlayerAnglePI2と0~1の間で返ってくるGetPlayerAngle1を用意しました。それに伴いCBulletクラスも少し修正をしてます。

public class CBullet : CMover
{
public int State { set; get; } = 0;
public double Speed { set; get; } = 0.0;
public double Angle { set; get; } = 0.0;
public double BaseAngle { set; get; } = 0.0;
public int Color { set; get; }
int GraphType, Rotate;
double Yaw, VYaw;// 回転角度
double AngleRate, SpeedRate;
double PI2or1, AddRotate;
public CBullet(double x, double y, double speed = 3.0, double angle = 0, double speed_rate = 0.0, double angle_rate = 0.0,
int graph_type = 0, int color = 0, int state = -1, int rotate = 0, bool se_flag = true, int effect_type = 0, double pi2or1 = PI2) : bas y, 1, graph_type)
{
State = state;
Angle = angle;
AngleRate = angle_rate;
Speed = speed; 
SpeedRate = speed_rate;
GraphType = graph_type;
Color = color; 
Rotate = rotate;
PI2or1 = pi2or1; // これにはPI2か1.0のどちらかを入れるようにする
// SGP.GetPlayerAnglePI2を使った時には1.0を入れる事
// GetPlayerAngle1を使った時にはPI2を入れる事
if (PI2or1 == 1.0)
{
AddRotate = PI/2;
}
else
{
AddRotate = 0.25;
}
Yaw = 0.0;
VYaw = 0.0;
// 弾画像によって当たり判定を変える
switch (graph_type)
{
case 0: // でかい丸の弾
L = T = -9.0f;
R = B = 9.0f;
break;
case 1: // 丸の弾
L = T = -4.0f;
R = B = 4.0f;
break;
case 2: // 線の弾
L = T = -5.0f;
R = B = 5.0f;
break;
case 3: // 剣の弾
L = T = -4.0f;
R = B = 4.0f;
break;
case 4: // 光る弾
L = T = -4.0f;
R = B = 4.0f;
break;
case 5: // お札弾
L = T = -4.0f;
R = B = 4.0f;
break;
case 6: // 孔雀弾
L = T = -3.0f;
R = B = 3.0f;
break;
case 7: // 丸+四角弾
L = T = -3.0f;
R = B = 3.0f;
break;
case 8: // 米弾
L = T = -3.0f;
R = B = 3.0f;
break;
case 9:
L = T = -3.0f;
R = B = 3.0f;
break;
case 10: // 粒弾
L = T = -2.0f;
R = B = 2.0f;
break;
case 11: // 蝶弾
L = T = -4.0f;
R = B = 4.0f;
break;
case 12:
L = T = -3.0f;
R = B = 3.0f;
break;
case 13:
L = T = -3.0f;
R = B = 3.0f;
break;
case 14:
L = T = -1.0f;
R = B = 1.0f;
break;
case 15: // 花弾
L = T = -4.0f;
R = B = 4.0f;
break;
default: // 指定した画像タイプが範囲外なら通常弾画像にする
GraphType = 1;
L = T = -4.0f;
R = B = 4.0f;
break;
}
if (se_flag)
{
SGP.PlaySound(0);
} 
}
public virtual void Move()
{
//弾を回転させる
if (Rotate == 1) Yaw += VYaw;
//角度をラジアン単位に変換する
double rad = Angle * PI2or1;
// 角度と速度を使って、座標を更新する
X += Speed * Math.Cos(rad);
Y += Speed * Math.Sin(rad);
// 角度に角速度を加算する
Angle += AngleRate;
// 速度に加速度を加算する
Speed += SpeedRate;
if (X < 10 + 20 || // 左座標の上限
X > FIELD_MAX_X - 10 + 42 || // 右座標の上限
Y < 0 || // 上座標の上限
Y > FIELD_MAX_Y - 5 + 20) // 下座標の上限
{
Life = 0;
}
++Cnt;
}
public void Draw()
{
DX.DrawRotaGraphF((float)X, (float)Y, 1.0, (Angle + AddRotate + Yaw) * PI2or1, SGP.ImageBullet[GraphType][Color], 1);
//DX.DrawBox((int)(X + L), (int)(Y + T), (int)(X + R), (int)(Y + B), DX.GetColor(0, 0, 255), 1);
}
}

ここで重要なのは、GetPlayerAnglePI2を使う時には引数のPI2or1に1.0をGetPlayerAngle1を使う時には PI2or1にPI2を入れるということです。雑魚の弾幕では0~1.0を使い、ボスの弾幕では0~PI2を使う事が多いので、両方使える仕様に変更しました。

次はボスの変数と関数がごちゃごちゃになってきたので、移動と弾幕のクラスを分離させる作業をする予定です。