ホーミングレーザー
https://gyazo.com/7e874fd5bdd815d7ff88b84972770587
code:siv_trail_homing_laser.cpp
# include <Siv3D.hpp> // OpenSiv3D v0.6.11
namespace
{
// 角度差
double AbsAngularDifference(double theta1, double theta2)
{
const double d1 = Abs(theta2 - theta1);
const double t1 = theta1 < 0 ? theta1 + Math::TwoPi : theta1;
const double t2 = theta2 < 0 ? theta2 + Math::TwoPi : theta2;
const double d2 = Abs(t2 - t1);
return Min(d1, d2);
}
}
struct HomingLaser
{
enum class RotateDirection
{
Left,
Right,
};
// レーザーの先端の位置
Vec2 pos;
// 進む速さと角度
Circular speed;
// 角度を補正する方向
RotateDirection rotateDirection;
// これがRunningの間はプレイヤーを追尾する
Stopwatch timerHoming;
// 生成されてからの時間
Stopwatch timer;
// 軌跡
Array<TrailMotion> trails;
HomingLaser(const Vec2& initialPos, double initialAngle, RotateDirection rotateDir)
:
pos{ initialPos },
speed{ 4.0, initialAngle },
rotateDirection{ rotateDir },
timerHoming{ StartImmediately::Yes },
timer{ StartImmediately::Yes }
{
// 軌跡
// いい感じになるように適当に追加してみる...
// ガワ
trails << TrailMotion{}
.setPositionFunction(&(double t) { return pos; }) .setSizeFunction([](double t) { return 48 * 0.5 * (1 + Sin(t * 3.0)); })
.setScaleFunction([](double t) { return t; })
.setColorFunction([](double t) {
const auto hsv = HSV{ Palette::Lime };
return HSV{ hsv.h + t * 40, hsv.s, hsv.v };
});
// 芯
trails << TrailMotion{}
.setPositionFunction(&(double t) { return pos; }) .setSizeFunction([](double t) { return 24 * 0.5 * (1 + Sin(t * 3.0)); });
// ギザギザ
trails << TrailMotion{}
.setPositionFunction(&(double t) { return pos + RandomVec2() * 8.0; }) .setSizeFunction([](double t) { return 32 * Periodic::Triangle0_1(0.08s); })
.setColor(Palette::Cyan);
}
void update(const Vec2& playerPos)
{
// 追尾
if (timerHoming.isRunning() && timerHoming.sF() < 1.2)
{
const double angleToPlayer = Atan2(-(pos.x - playerPos.x), pos.y - playerPos.y);
const double angleDiff = AbsAngularDifference(angleToPlayer, speed.theta);
if (pos.distanceFrom(playerPos) < 128 || angleDiff < 8_deg)
{
// プレイヤーに近づきすぎた or 狙った角度になった
// ので追尾をやめる
timerHoming.reset();
}
else
{
// 進む角度をプレイヤーに向ける
const double rotSign = (rotateDirection == RotateDirection::Right) ? 1 : -1;
const double rotAdd = Min(angleDiff * 0.90, Math::TwoPi / 2 * Scene::DeltaTime());
speed.theta += rotSign * rotAdd;
}
}
// 速度調整
if (timerHoming.isRunning() && timerHoming.sF() < 0.35)
{
speed.r -= 3 * Scene::DeltaTime();
}
else
{
speed.r += 20.0 * Scene::DeltaTime();
}
pos += speed;
// 軌跡を更新
for (auto& trail : trails)
{
trail.update();
}
}
void draw() const
{
const ScopedRenderStates2D blend{ BlendState::Additive };
for (const auto& trail : trails)
{
trail.draw(TextureAsset(U"laser"));
}
}
};
void Main()
{
Scene::SetBackground(ColorF{ 0.05 });
// レーザー軌跡用のテクスチャ
TextureAsset::Register(U"laser", U"example/particle.png", TextureDesc::Mipped);
// レーザーのリスト
Array<std::unique_ptr<HomingLaser>> homingLaserList;
// レーザー発射間隔を管理するタイマー
Stopwatch timerFireLaser{ StartImmediately::Yes };
// レーザー発射位置
const auto enemyPos = Scene::CenterF().movedBy(0, -230);
while (System::Update())
{
// 一定間隔でレーザーを発射
if (timerFireLaser.sF() > 1.0)
{
timerFireLaser.restart();
homingLaserList.push_back(std::make_unique<HomingLaser>(enemyPos, 45_deg, HomingLaser::RotateDirection::Right));
homingLaserList.push_back(std::make_unique<HomingLaser>(enemyPos, -45_deg, HomingLaser::RotateDirection::Left));
}
// プレイヤーの位置(マウスカーソル位置)
const auto playerPos = Cursor::PosF();
// レーザーを更新
for (auto& laser : homingLaserList)
{
laser->update(playerPos);
}
// 十分に画面外に出たレーザーを削除
Erase_if(homingLaserList, &(const auto& laser) { return not Scene::Rect().stretched(256).intersects(laser->pos); }); // レーザー発射位置を描画
RectF{ Arg::center = enemyPos, 32 }.rotated(45_deg).draw(ColorF{ 1.0, 0.5 + 0.5 * Periodic::Jump0_1(0.6s) });
// プレイヤー位置
const ColorF playerColor{ 1.0, 0.5 + 0.5 * Periodic::Sine0_1(0.6s) };
Shape2D::Cross(28, 2).asPolygon().rotate(45_deg).movedBy(playerPos).draw(playerColor);
Circle{ playerPos, 20 }.drawFrame(2, playerColor);
// レーザーを描画
for (const auto& laser : homingLaserList)
{
laser->draw();
}
}
}