#はじめに
OpenSiv3D 0.6.3がリリースされていること、そして要件にMicrosoft Visual C++ 2022とあり試しにインストール。
楽しみにしていたホットリロードに感動しながら色々してできたのが以下の簡単なゲームアプリです。よく分からないものができてしまいました。
#実装
鳥やタマゴ、プレイヤーはクラス化し、ゲームの実装は大きなプログラムでもないためメイン関数内に直接書きました。シーン管理もswitch文で簡単に済ませています。
テクスチャはEmojiから作成しています。リテラルから作成する場合は、
Texture texture(U"🐣"_emoji);
です。テクスチャに困ったらとりあえずEmoji使ってるくらい、大変便利な機能です。
Emojiは他にもたくさんあり、Siv3Dリファレンスv0.6.3によるとemojipediaで調べるのが便利だそうです。
鳥の出現確率やタマゴの発生確率は適当です。ただしフレームレートの違いを考慮してScene::DeltaTime()を使っています。
#include<Siv3D.hpp>//0.6.3
class AliveTime {
public:
SecondsF aliveTime() const {
return sw.elapsed();
}
private:
Stopwatch sw{ StartImmediately::Yes };
};
class Bird :public AliveTime {
public:
Bird(const Vec2& pos, const Vec2& vel) :
pos(pos), vel(vel) {}
void update() {
const double delta = Scene::DeltaTime();
if (RandomBool(0.1 * delta)) {
vel.y *= -1;
}
pos.moveBy(vel * delta);
}
void draw() const {
const Texture& texture = getTexture();
if (vel.x > 0) {
texture.mirrored().drawAt(pos);
}
else {
texture.drawAt(pos);
}
}
private:
static const Texture& getTexture() {
static const Texture texture{ U"🐦"_emoji };
return texture;
}
public:
Vec2 pos;
Vec2 vel;
};
class Egg :public AliveTime {
public:
Egg(const Vec2& pos, const Vec2& vel) :
pos(pos), vel(vel) {}
void update() {
const double delta = Scene::DeltaTime();
vel.x *= Pow(0.36, delta);
vel.y += 150.0 * delta;
pos.moveBy(vel * delta);
}
void draw() const {
getTexture().scaled(0.3).drawAt(pos);
}
Circle getRegion() const {
return Circle(pos, 12.0);
}
private:
static const Texture& getTexture() {
static const Texture texture{ U"🥚"_emoji };
return texture;
}
public:
Vec2 pos;
Vec2 vel;
};
class Chick {
public:
void update() {
pos.set(Clamp(Cursor::PosF().x, 15.0, Scene::Width() - 15.0), 550);
}
void draw() const {
getTexture().scaled(0.3).drawAt(pos);
}
Circle getRegion() const {
return Circle(pos, 15);
}
private:
static const Texture& getTexture() {
static const Texture texture{ U"🐣"_emoji };
return texture;
}
public:
Vec2 pos;
};
class Hit :public IEffect {
public:
Hit(const Vec2& pos) :
pos(pos) {}
bool update(double t) override {
getTexture().scaled(EaseOutBounce(EaseOutQuad(Math::InvLerp(0, 3, t)))).drawAt(pos);
return t < 3;
}
private:
static const Texture& getTexture() {
static const Texture texture{ U"💥"_emoji };
return texture;
}
Vec2 pos;
};
enum class GameState {
Title, Game, GameOver,
};
void Main() {
Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 });
Window::SetTitle(U"タマゴ避けるナゾゲー");
const Font font(50);
const Font emojiFont{ 50, Typeface::ColorEmoji };
font.addFallback(emojiFont);
GameState state = GameState::Title;
Array<Bird> birds;
Array<Egg> eggs;
Chick chick;
Effect effect;
Stopwatch sw;
while (System::Update()) {
const double delta = Scene::DeltaTime();
switch (state) {
case GameState::Title:
{
font(U"避けゲー").drawAt(Scene::Center() + Vec2::Up(30), Palette::Black);
font(U"Press Enter Key").drawAt(Scene::Center() + Vec2::Down(30), Palette::Black);
if (KeyEnter.down()) {
birds.clear();
eggs.clear();
effect.clear();
sw.restart();
state = GameState::Game;
}
}
break;
case GameState::Game:
{
chick.update();
if (RandomBool((1 + 0.1 * sw.sF()) * delta)) {
if (RandomBool()) {
birds.emplace_back(Vec2(-100, Random(50.0, 150.0)), Vec2(Random(70.0, 130.0), Random(-15.0, 15.0)));
}
else {
birds.emplace_back(Vec2(Scene::Width() + 100, Random(50.0, 150.0)), Vec2(Random(-130.0, -70.0), Random(-15.0, 15.0)));
}
}
for (auto&& bird : birds) {
bird.update();
}
birds.remove_if([](const Bird& bird) {return bird.aliveTime() > 20s; });
for (const auto& bird : birds) {
if (RandomBool(0.01 * sw.sF() * delta)) {
eggs.emplace_back(bird.pos, Vec2(bird.vel.x, Random(80.0, 120.0)));
}
}
for (auto&& egg : eggs) {
egg.update();
}
eggs.remove_if([](const Egg& egg) {return egg.aliveTime() > 5s; });
for (auto i = eggs.begin(); i != eggs.end();) {
if (chick.getRegion().intersects(i->getRegion())) {
effect.add(std::make_unique<Hit>(i->pos));
sw.pause();
state = GameState::GameOver;
i = eggs.erase(i);
}
else {
++i;
}
}
effect.update();
for (const auto& egg : eggs) {
egg.draw();
}
for (const auto& bird : birds) {
bird.draw();
}
chick.draw();
font(sw.elapsed()).draw(0, 0, Palette::Black);
}
break;
case GameState::GameOver:
{
for (const auto& bird : birds) {
if (RandomBool(0.01 * sw.sF() * delta)) {
eggs.emplace_back(bird.pos, Vec2(bird.vel.x, Random(80.0, 120.0)));
}
}
for (auto&& egg : eggs) {
egg.update();
}
eggs.remove_if([](const Egg& egg) {return egg.aliveTime() > 20s; });
for (auto i = eggs.begin(); i != eggs.end();) {
if (chick.getRegion().intersects(i->getRegion())) {
effect.add(std::make_unique<Hit>(i->pos));
i = eggs.erase(i);
}
else {
++i;
}
}
effect.update();
for (const auto& egg : eggs) {
egg.draw();
}
for (const auto& bird : birds) {
bird.draw();
}
chick.draw();
Scene::Rect().draw(Palette::Black.withAlpha(150));
font(sw.elapsed()).draw(0, 0, Palette::Black);
font(U"Game Over!🐣").drawAt(Scene::Center() + Vec2::Up(30), Palette::White);
font(U"Press Enter Key").drawAt(Scene::Center() + Vec2::Down(30), Palette::White);
if (KeyEnter.down()) {
state = GameState::Title;
}
}
break;
}
}
}
#終わりに
ホットリロード、とても良かったです。最高です。
毎回数値を少し変えるだけなのにわざわざアプリを再起動する手間が省けて効率化に繋がりました。
#追記
Siv3D作者様のコメントより
Texture::mirrored()にはTexture::mirrored(bool)のオーバーロードがあるようで、Bird::draw()内の条件分岐は
texture.mirrored(vel.x > 0).drawAt(pos);
で置き換えられました。
上下反転のflip()にも同様のオーバーロードがあります。
これは自分で気づきませんでした。ソースコードまで目を通していただきありがとうございます。