Skip to content

Instantly share code, notes, and snippets.

@voidproc
Last active June 24, 2024 11:27
Show Gist options
  • Save voidproc/74eaa2835e8e263342d92a3e231c2dfa to your computer and use it in GitHub Desktop.
Save voidproc/74eaa2835e8e263342d92a3e231c2dfa to your computer and use it in GitHub Desktop.
#include <Siv3D.hpp> // Siv3D v0.6.14
#include <CoTaskLib.hpp> // CoTaskLib v0.2.0
// ゲームオブジェクト
// 生成後、run()を実行してから破棄されるまでタスクを実行し続ける
class GameObject : public Co::SequenceBase<>
{
public:
GameObject()
{
}
// タスクの実行が完了したか
bool done() const
{
return runner_ && runner_->done();
}
void run()
{
if (not runner_)
{
runner_.emplace(playScoped());
}
}
private:
Optional<Co::ScopedTaskRunner> runner_;
};
// 位置、当たり判定を持つゲームオブジェクト
class Character : public GameObject
{
public:
Character()
: GameObject{}
{
}
virtual Vec2 pos() const
{
return Vec2::Zero();
}
virtual RectF collision() const
{
return RectF{};
}
bool collided(const Character& other) const
{
return collision().intersects(other.collision());
}
virtual void damage()
{
}
};
template <class Obj>
struct GameObjectList : public Array<std::shared_ptr<Obj>>
{
public:
template <class T, class ...Args>
void pushAndRun(Args&& ...args)
{
this->push_back(std::make_shared<T>(std::forward<Args>(args)...));
this->back()->run();
}
void removeDone()
{
this->remove_if([](const auto& obj) { return obj->done(); });
}
};
// 共有データ
struct GameData
{
GameObjectList<Character> enemies;
GameObjectList<Character> effects;
GameObjectList<Character> players;
bool showCollision = false;
};
// 敵の弾
class Bullet1 : public Character
{
public:
Bullet1(const Vec2& pos, const Vec2& vel)
: Character{}, pos_{ pos }, vel_{ vel }
{
}
Vec2 pos() const override
{
return pos_;
}
RectF collision() const override
{
return RectF{ Arg::center = pos_, 12 };
}
private:
Co::Task<> start() override
{
const auto velocityChangeRunner = Co::Ease(&vel_, 0.7s, EaseInLinear).fromTo(vel_ * 0.5, vel_).playScoped();
const auto scaleChangeRunner = Co::Ease(&scale_, 0.3s, EaseOutSine).fromTo(3.0, 1.0).playScoped();
co_await Co::Any(
Co::Delay(5s),
Co::UpdaterTask([this]
{
pos_ += vel_ * Scene::DeltaTime();
}));
}
void draw() const override
{
Circle{ pos_, 12 * scale_ }
.drawFrame(8.0, Palette::Darkorange.lerp(Palette::Orange, Periodic::Sine0_1(0.1s)))
.draw(Palette::Yellow.lerp(Palette::Orange, Periodic::Sine0_1(0.1s)));
}
private:
Vec2 pos_;
Vec2 vel_;
double scale_ = 3.0;
};
// 敵弾が発射時エフェクト
class FireEffect : public Character
{
public:
FireEffect(const Vec2& pos)
: Character{}, pos_{ pos }, timer_{ 0.3s, StartImmediately::Yes }
{
}
private:
Co::Task<> start() override
{
co_await Co::WaitForTimer(&timer_);
}
void draw() const override
{
const double t = timer_.progress0_1();
Circle{ pos_, 12 + 32 * EaseOutSine(t) }.drawFrame(12 - 8 * t, Palette::Orange.lerp(Palette::Orange.withA(64), t));
}
Vec2 pos_;
Timer timer_;
};
// 当たり判定を描画するタスク
class CollisionDrawer : public Co::SequenceBase<>
{
public:
CollisionDrawer(GameData* gameData)
: Co::SequenceBase<>{ Co::Layer::Debug }, gameData_{ gameData }
{
}
Co::Task<> start() override
{
co_await Co::WaitForever();
}
void draw() const override
{
if (not gameData_->showCollision) return;
const auto lineWidth = 2.0;
for (const auto& enemy : gameData_->enemies)
{
enemy->collision().drawFrame(lineWidth, Palette::Magenta);
}
for (const auto& effect : gameData_->effects)
{
effect->collision().drawFrame(lineWidth, Palette::Magenta);
}
for (const auto& player : gameData_->players)
{
player->collision().drawFrame(lineWidth, Palette::Magenta);
}
}
private:
GameData* gameData_;
};
// 敵キャラ
class Chara1 : public Character
{
public:
Chara1(const Vec2& pos, GameData* gameData)
: Character{}, pos_{ pos }, gameData_{ gameData }
{
vel_ = Circular{ 200 + Random(-80.0, 150.0), (pos.x < Scene::CenterF().x ? 90_deg : 270_deg) + Random(-10_deg, 10_deg) };
}
Vec2 pos() const override
{
return pos_;
}
RectF collision() const override
{
return RectF{ Arg::center = pos_, 24 };
}
private:
Co::Task<> start() override
{
const auto fireRunner = fire_().runScoped();
co_await Co::Any(
Co::Delay(10s),
Co::WaitWhile([this]
{
return Scene::Rect().stretched(32).contains(pos_);
}),
Co::UpdaterTask([this]
{
pos_ += vel_ * Scene::DeltaTime();
}));
}
Co::Task<> fire_()
{
while (true)
{
co_await Co::Delay(2s);
const Vec2 playerPos = gameData_->players.size() > 0 ? gameData_->players.front()->pos() : Vec2::Zero();
const double bulletSpeed = 250 + Random(-50.0, 300.0);
const double bulletAngle = Atan2(-(pos_.x - playerPos.x), (pos_.y - playerPos.y));
gameData_->enemies.pushAndRun<Bullet1>(pos_, Circular{ bulletSpeed, bulletAngle });
if (RandomBool(0.1))
{
gameData_->enemies.pushAndRun<Bullet1>(pos_, Circular{ bulletSpeed, bulletAngle + 10_deg });
gameData_->enemies.pushAndRun<Bullet1>(pos_, Circular{ bulletSpeed, bulletAngle - 10_deg });
}
gameData_->effects.pushAndRun<FireEffect>(pos_);
}
}
void draw() const override
{
Circle{ pos_, 24 }
.drawFrame(6, Palette::Gray)
.draw();
}
Vec2 pos_;
Vec2 vel_;
GameData* gameData_;
};
// プレイヤー
// マウスに追従する
class Player : public Character
{
public:
Player()
: Character{}, pos_{}, tex_{ U"😊"_emoji }
{
}
Vec2 pos() const override
{
return pos_;
}
RectF collision() const override
{
return RectF{ Arg::center = pos_, 16 };
}
void damage() override
{
damageRunner_.emplace(damage_());
}
private:
Co::Task<> start() override
{
while (true)
{
pos_ = Cursor::PosF();
co_await Co::NextFrame();
}
}
void draw() const override
{
const Color tint = (damageRunner_ && not damageRunner_->done()) ? Palette::Orange : Palette::White;
Circle{ pos_ + offset_, 46 }.draw(Palette::White.withA(127 + 80 * Periodic::Sine0_1(0.2s)), Palette::Yellow.withA(0));
tex_.resized(64).drawAt(pos_ + offset_, tint);
}
Co::Task<> damage_()
{
co_await(Co::Any(
Co::Delay(0.5s),
Co::UpdaterTask([this]
{
offset_.x = 16 * Periodic::Sine1_1(0.08s);
})));
offset_ = Vec2::Zero();
}
Vec2 pos_;
Vec2 offset_{};
Texture tex_;
Optional<Co::ScopedTaskRunner> damageRunner_;
};
class MainScene : public Co::SceneBase
{
public:
MainScene(GameData* gameData)
: gameData_{ gameData }
{
}
private:
Co::Task<> start() override
{
gameData_->players.pushAndRun<Player>();
const auto generateCharaRunner = generateChara_().runScoped();
const auto collisionCheckRunner = collisionCheck_().runScoped();
while (true)
{
if (KeyC.down())
{
gameData_->showCollision = not gameData_->showCollision;
Print << (gameData_->showCollision ? U"当たり判定を表示" : U"当たり判定を非表示");
}
co_await Co::NextFrame();
}
}
void draw() const override
{
// 背景
constexpr int N = 8;
for (int i = 0; i < N; ++i)
{
const int distance = Scene::Height() / N;
const double scroll = ((int)(Scene::Time() * 120.0)) % distance;
const double y = Scene::Rect().topY() + distance * i + scroll;
Line{ 0, y, Scene::Width(), y }.draw(Palette::Orange.withA(128));
}
}
Co::Task<> generateChara_()
{
while (true)
{
const Vec2 pos{ Sample({ -16, 800 + 16 }), Random(20, 240) };
gameData_->enemies.pushAndRun<Chara1>(pos, gameData_);
co_await Co::Delay(0.2s);
}
}
Co::Task<> collisionCheck_()
{
while (true)
{
if (gameData_->players.size() > 0)
{
auto player = gameData_->players[0];
for (size_t iEnemy = 0; iEnemy < gameData_->enemies.size(); ++iEnemy)
{
if (auto enemy = gameData_->enemies[iEnemy];
player->collided(*enemy))
{
player->damage();
gameData_->effects.pushAndRun<FireEffect>(enemy->pos());
co_await Co::Delay(0.5s);
}
}
}
co_await Co::NextFrame();
}
}
GameData* gameData_;
};
void Main()
{
Scene::SetBackground(ColorF{ 0.1 });
Co::Init();
GameData gameData;
const auto sceneRunner = Co::PlaySceneFrom<MainScene>(&gameData).runScoped();
const auto collisionDrawer = Co::Play<CollisionDrawer>(&gameData).runScoped();
while (System::Update())
{
if (sceneRunner.done())
{
break;
}
PutText(Format(U"enemy:", gameData.enemies.size(), U"\neffect:", gameData.effects.size()),
Arg::topRight = Vec2{ Scene::Width() - 8, 8 });
gameData.enemies.removeDone();
gameData.effects.removeDone();
}
}
@voidproc
Copy link
Author

screenshot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment