-
-
Save voidproc/74eaa2835e8e263342d92a3e231c2dfa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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(); | |
} | |
} |
Author
voidproc
commented
Jun 23, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment