1. クッキークリッカーの改良¶
1.1 桁区切り記号を付ける¶
- 整数に桁区切り記号を付けるには
String ThousandsSeparate(value, separator)
を使います
# include <Siv3D.hpp>
void Main()
{
Print << 123456789;
Print << ThousandsSeparate(123456789);
Print << ThousandsSeparate(123456789, U" ");
while (System::Update())
{
}
}
- クッキーの枚数に桁区切り記号を付けます
# include <Siv3D.hpp>
/// @brief アイテムのボタン
/// @param rect ボタンの領域
/// @param texture ボタンの絵文字
/// @param font 文字描画に使うフォント
/// @param name アイテムの名前
/// @param desc アイテムの説明
/// @param count アイテムの所持数
/// @param enabled ボタンを押せるか
/// @return ボタンが押された場合 true, それ以外の場合は false
bool Button(const Rect& rect, const Texture& texture, const Font& font, const String& name, const String& desc, int32 count, bool enabled)
{
if (enabled)
{
rect.draw(ColorF{ 0.3, 0.5, 0.9, 0.8 });
rect.drawFrame(2, 2, ColorF{ 0.5, 0.7, 1.0 });
if (rect.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
}
else
{
rect.draw(ColorF{ 0.0, 0.4 });
rect.drawFrame(2, 2, ColorF{ 0.5 });
}
texture.scaled(0.5).drawAt(rect.x + 50, rect.y + 50);
font(name).draw(30, rect.x + 100, rect.y + 15, Palette::White);
font(desc).draw(18, rect.x + 102, rect.y + 60, Palette::White);
font(count).draw(50, Arg::rightCenter((rect.x + rect.w - 20), (rect.y + 50)), Palette::White);
return (enabled && rect.leftClicked());
}
void Main()
{
// クッキーの絵文字
Texture texture{ U"🍪"_emoji };
// 農場の絵文字
Texture farmEmoji{ U"🌾"_emoji };
// 工場の絵文字
Texture factoryEmoji{ U"🏭"_emoji };
// フォント
Font font{ FontMethod::MSDF, 48, Typeface::Bold };
// クッキーのクリック円
Circle cookieCircle{ 170, 300, 100 };
// クッキーの表示サイズ(倍率)
double cookieScale = 1.5;
// クッキーの個数
double cookies = 0;
// 農場の所有数
int32 farmCount = 0;
// 工場の所有数
int32 factoryCount = 0;
// 農場の価格
int32 farmCost = 10;
// 工場の価格
int32 factoryCost = 100;
// クッキーの毎秒の生産量
int32 cps = 0;
// ゲームの経過時間の蓄積
double accumulatedTime = 0.0;
while (System::Update())
{
// クッキーの毎秒の生産量を計算する
cps = (farmCount + factoryCount * 10);
// ゲームの経過時間を加算する
accumulatedTime += Scene::DeltaTime();
// 0.1 秒以上蓄積していたら
if (0.1 <= accumulatedTime)
{
accumulatedTime -= 0.1;
// 0.1 秒分のクッキー生産を加算する
cookies += (cps * 0.1);
}
// 農場の価格を計算する
farmCost = 10 + (farmCount * 10);
// 工場の価格を計算する
factoryCost = 100 + (factoryCount * 100);
// クッキー円上にマウスカーソルがあれば
if (cookieCircle.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
// クッキー円が左クリックされたら
if (cookieCircle.leftClicked())
{
cookieScale = 1.3;
++cookies;
}
// クッキーの表示サイズを回復する
cookieScale += Scene::DeltaTime();
if (1.5 < cookieScale)
{
cookieScale = 1.5;
}
// 背景を描く
Rect{ 0, 0, 800, 600 }.draw(Arg::top = ColorF{ 0.6, 0.5, 0.3 }, Arg::bottom = ColorF{ 0.2, 0.5, 0.3 });
// クッキーの数を整数で表示する
font(ThousandsSeparate((int32)cookies)).drawAt(60, 170, 100);
// クッキーの生産量を表示する
font(U"毎秒: {}"_fmt(cps)).drawAt(24, 170, 160);
// クッキーを描画する
texture.scaled(cookieScale).drawAt(cookieCircle.x, cookieCircle.y);
// 農場ボタン
if (Button(Rect{ 340, 40, 420, 100 }, farmEmoji, font, U"クッキー農場", U"C{} / 1 CPS"_fmt(farmCost), farmCount, (farmCost <= cookies)))
{
cookies -= farmCost;
++farmCount;
}
// 工場ボタン
if (Button(Rect{ 340, 160, 420, 100 }, factoryEmoji, font, U"クッキー工場", U"C{} / 10 CPS"_fmt(factoryCost), factoryCount, (factoryCost <= cookies)))
{
cookies -= factoryCost;
++factoryCount;
}
}
}
1.2 押し心地を改良する¶
- ばねの動きを次のように再現できます
# include <Siv3D.hpp>
void Main()
{
// ばねの伸び
double springX = 0.0;
// ばねの速度
double springVelocity = 0.0;
// ばねの蓄積時間
double springAccumulatedTime = 0.0;
while (System::Update())
{
springAccumulatedTime += Scene::DeltaTime();
while (0.005 <= springAccumulatedTime)
{
// ばねの力(変化を打ち消す方向)
double force = (-0.02 * springX);
// 画面を押しているときに働く力
if (MouseL.pressed())
{
force += 4.0;
}
// 速度に力を適用(減衰もさせる)
springVelocity = (springVelocity + force) * 0.92;
// 位置に反映
springX += springVelocity;
springAccumulatedTime -= 0.005;
}
Circle{ 400 + springX, 300, 40 }.draw();
}
}
- クッキーを押したときの大きさの変化をばねの動きに対応させます
# include <Siv3D.hpp>
/// @brief アイテムのボタン
/// @param rect ボタンの領域
/// @param texture ボタンの絵文字
/// @param font 文字描画に使うフォント
/// @param name アイテムの名前
/// @param desc アイテムの説明
/// @param count アイテムの所持数
/// @param enabled ボタンを押せるか
/// @return ボタンが押された場合 true, それ以外の場合は false
bool Button(const Rect& rect, const Texture& texture, const Font& font, const String& name, const String& desc, int32 count, bool enabled)
{
if (enabled)
{
rect.draw(ColorF{ 0.3, 0.5, 0.9, 0.8 });
rect.drawFrame(2, 2, ColorF{ 0.5, 0.7, 1.0 });
if (rect.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
}
else
{
rect.draw(ColorF{ 0.0, 0.4 });
rect.drawFrame(2, 2, ColorF{ 0.5 });
}
texture.scaled(0.5).drawAt(rect.x + 50, rect.y + 50);
font(name).draw(30, rect.x + 100, rect.y + 15, Palette::White);
font(desc).draw(18, rect.x + 102, rect.y + 60, Palette::White);
font(count).draw(50, Arg::rightCenter((rect.x + rect.w - 20), (rect.y + 50)), Palette::White);
return (enabled && rect.leftClicked());
}
void Main()
{
// クッキーの絵文字
Texture texture{ U"🍪"_emoji };
// 農場の絵文字
Texture farmEmoji{ U"🌾"_emoji };
// 工場の絵文字
Texture factoryEmoji{ U"🏭"_emoji };
// フォント
Font font{ FontMethod::MSDF, 48, Typeface::Bold };
// クッキーのクリック円
Circle cookieCircle{ 170, 300, 100 };
// ばねの伸び
double springX = 0.0;
// ばねの速度
double springVelocity = 0.0;
// ばねの蓄積時間
double springAccumulatedTime = 0.0;
// クッキーの個数
double cookies = 0;
// 農場の所有数
int32 farmCount = 0;
// 工場の所有数
int32 factoryCount = 0;
// 農場の価格
int32 farmCost = 10;
// 工場の価格
int32 factoryCost = 100;
// クッキーの毎秒の生産量
int32 cps = 0;
// ゲームの経過時間の蓄積
double accumulatedTime = 0.0;
while (System::Update())
{
// クッキーの毎秒の生産量を計算する
cps = (farmCount + factoryCount * 10);
// ゲームの経過時間を加算する
accumulatedTime += Scene::DeltaTime();
// 0.1 秒以上蓄積していたら
if (0.1 <= accumulatedTime)
{
accumulatedTime -= 0.1;
// 0.1 秒分のクッキー生産を加算する
cookies += (cps * 0.1);
}
// ばねの蓄積時間を加算する
springAccumulatedTime += Scene::DeltaTime();
while (0.005 <= springAccumulatedTime)
{
// ばねの力(変化を打ち消す方向)
double force = (-0.02 * springX);
// 画面を押しているときに働く力
if (cookieCircle.leftPressed())
{
force += 0.004;
}
// 速度に力を適用(減衰もさせる)
springVelocity = (springVelocity + force) * 0.92;
// 位置に反映
springX += springVelocity;
springAccumulatedTime -= 0.005;
}
// 農場の価格を計算する
farmCost = 10 + (farmCount * 10);
// 工場の価格を計算する
factoryCost = 100 + (factoryCount * 100);
// クッキー円上にマウスカーソルがあれば
if (cookieCircle.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
// クッキー円が左クリックされたら
if (cookieCircle.leftClicked())
{
++cookies;
}
// 背景を描く
Rect{ 0, 0, 800, 600 }.draw(Arg::top = ColorF{ 0.6, 0.5, 0.3 }, Arg::bottom = ColorF{ 0.2, 0.5, 0.3 });
// クッキーの数を整数で表示する
font(ThousandsSeparate((int32)cookies)).drawAt(60, 170, 100);
// クッキーの生産量を表示する
font(U"毎秒: {}"_fmt(cps)).drawAt(24, 170, 160);
// クッキーを描画する
texture.scaled(1.5 - springX).drawAt(cookieCircle.x, cookieCircle.y);
// 農場ボタン
if (Button(Rect{ 340, 40, 420, 100 }, farmEmoji, font, U"クッキー農場", U"C{} / 1 CPS"_fmt(farmCost), farmCount, (farmCost <= cookies)))
{
cookies -= farmCost;
++farmCount;
}
// 工場ボタン
if (Button(Rect{ 340, 160, 420, 100 }, factoryEmoji, font, U"クッキー工場", U"C{} / 10 CPS"_fmt(factoryCost), factoryCount, (factoryCost <= cookies)))
{
cookies -= factoryCost;
++factoryCount;
}
}
}
1.3 クリック時のエフェクトを追加する¶
Effect
を使うと、複数フレーム間にまたがるアニメーションを簡単に記述できます- クリックしたときにクッキーが舞うエフェクトや、「+1」という文字が上昇するエフェクトを作ります
1.3.1 エフェクトの基本¶
エフェクト機能を使うには、エフェクトを管理する Effect
オブジェクトを作成し、IEffect
を継承した任意のクラス EffectType
を使って、Effect::add<EffectType>()
を通してアクティブなエフェクトを追加します。
IEffect
を継承するクラスに必要なメンバ関数の実装は bool update(double t) override
です。
この関数は、エフェクトが発生してからの経過時間 t
を受け取り、それに応じたエフェクトの描画を行います。戻り値として、エフェクトを次のフレームも継続させる場合 true
を、削除する場合は false
を返します。例えば return (t < 3.0);
とすれば、エフェクトは 3 秒間継続することになります。
Effect
は、時間ベースのアニメーションを簡単に作れるため、ゲームの演出などで重宝します。次のプログラムは、クリックした場所に、時間とともに大きくなる輪を 1 秒にわたって発生させるエフェクトの実装例です。
# include <Siv3D.hpp>
struct RingEffect : IEffect
{
Vec2 m_pos;
ColorF m_color;
// このコンストラクタ引数が、Effect::add<RingEffect>() の引数になる
explicit RingEffect(const Vec2& pos)
: m_pos{ pos }
, m_color{ RandomColorF() } {}
bool update(double t) override
{
// 時間に応じて大きくなる輪
Circle{ m_pos, (t * 100) }.drawFrame(4, m_color);
// 1 秒未満なら継続
return (t < 1.0);
}
};
void Main()
{
Effect effect;
while (System::Update())
{
ClearPrint();
// アクティブなエフェクトの数
Print << U"Active effects: {}"_fmt(effect.num_effects());
if (MouseL.down())
{
// エフェクトを発生
effect.add<RingEffect>(Cursor::Pos());
}
// アクティブなエフェクトのプログラム IEffect::update() を実行
effect.update();
}
}
1.3.2 クッキークリッカー用のエフェクト¶
# include <Siv3D.hpp>
// クッキーが舞うエフェクト
struct CookieEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 初速
Vec2 m_velocity;
// 拡大倍率
double m_scale;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_velocity{ Circular{ 80, Random(-40_deg, 40_deg) } }
, m_scale{ Random(0.2, 0.3) }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start
+ m_velocity * t + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(m_scale).rotated(m_angle).drawAt(pos, ColorF{1.0, (1.0 - t)});
return (t < 1.0);
}
};
// 「+1」が上昇するエフェクト
struct PlusOneEffect : IEffect
{
// 初期座標
Vec2 m_start;
// フォント
Font m_font;
PlusOneEffect(const Vec2& start, const Font& font)
: m_start{ start }
, m_font{ font } {}
bool update(double t) override
{
m_font(U"+1").drawAt(24, m_start.movedBy(0, t * -120), ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
void Main()
{
Texture texture{ U"🍪"_emoji };
Font font{ FontMethod::MSDF, 48, Typeface::Bold };
Effect effect;
while (System::Update())
{
if (MouseL.down())
{
effect.add<CookieEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-5, 5)), texture);
effect.add<PlusOneEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-15, -5)), font);
}
effect.update();
}
}
# include <Siv3D.hpp>
/// @brief アイテムのボタン
/// @param rect ボタンの領域
/// @param texture ボタンの絵文字
/// @param font 文字描画に使うフォント
/// @param name アイテムの名前
/// @param desc アイテムの説明
/// @param count アイテムの所持数
/// @param enabled ボタンを押せるか
/// @return ボタンが押された場合 true, それ以外の場合は false
bool Button(const Rect& rect, const Texture& texture, const Font& font, const String& name, const String& desc, int32 count, bool enabled)
{
if (enabled)
{
rect.draw(ColorF{ 0.3, 0.5, 0.9, 0.8 });
rect.drawFrame(2, 2, ColorF{ 0.5, 0.7, 1.0 });
if (rect.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
}
else
{
rect.draw(ColorF{ 0.0, 0.4 });
rect.drawFrame(2, 2, ColorF{ 0.5 });
}
texture.scaled(0.5).drawAt(rect.x + 50, rect.y + 50);
font(name).draw(30, rect.x + 100, rect.y + 15, Palette::White);
font(desc).draw(18, rect.x + 102, rect.y + 60, Palette::White);
font(count).draw(50, Arg::rightCenter((rect.x + rect.w - 20), (rect.y + 50)), Palette::White);
return (enabled && rect.leftClicked());
}
// クッキーが舞うエフェクト
struct CookieEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 初速
Vec2 m_velocity;
// 拡大倍率
double m_scale;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_velocity{ Circular{ 80, Random(-40_deg, 40_deg) } }
, m_scale{ Random(0.2, 0.3) }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start
+ m_velocity * t + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(m_scale).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
// 「+1」が上昇するエフェクト
struct PlusOneEffect : IEffect
{
// 初期座標
Vec2 m_start;
// フォント
Font m_font;
PlusOneEffect(const Vec2& start, const Font& font)
: m_start{ start }
, m_font{ font } {}
bool update(double t) override
{
m_font(U"+1").drawAt(24, m_start.movedBy(0, t * -120), ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
void Main()
{
// クッキーの絵文字
Texture texture{ U"🍪"_emoji };
// 農場の絵文字
Texture farmEmoji{ U"🌾"_emoji };
// 工場の絵文字
Texture factoryEmoji{ U"🏭"_emoji };
// フォント
Font font{ FontMethod::MSDF, 48, Typeface::Bold };
// エフェクト
Effect effect;
// クッキーのクリック円
Circle cookieCircle{ 170, 300, 100 };
// ばねの伸び
double springX = 0.0;
// ばねの速度
double springVelocity = 0.0;
// ばねの蓄積時間
double springAccumulatedTime = 0.0;
// クッキーの個数
double cookies = 0;
// 農場の所有数
int32 farmCount = 0;
// 工場の所有数
int32 factoryCount = 0;
// 農場の価格
int32 farmCost = 10;
// 工場の価格
int32 factoryCost = 100;
// クッキーの毎秒の生産量
int32 cps = 0;
// ゲームの経過時間の蓄積
double accumulatedTime = 0.0;
while (System::Update())
{
// クッキーの毎秒の生産量を計算する
cps = (farmCount + factoryCount * 10);
// ゲームの経過時間を加算する
accumulatedTime += Scene::DeltaTime();
// 0.1 秒以上蓄積していたら
if (0.1 <= accumulatedTime)
{
accumulatedTime -= 0.1;
// 0.1 秒分のクッキー生産を加算する
cookies += (cps * 0.1);
}
// ばねの蓄積時間を加算する
springAccumulatedTime += Scene::DeltaTime();
while (0.005 <= springAccumulatedTime)
{
// ばねの力(変化を打ち消す方向)
double force = (-0.02 * springX);
// 画面を押しているときに働く力
if (cookieCircle.leftPressed())
{
force += 0.004;
}
// 速度に力を適用(減衰もさせる)
springVelocity = (springVelocity + force) * 0.92;
// 位置に反映
springX += springVelocity;
springAccumulatedTime -= 0.005;
}
// 農場の価格を計算する
farmCost = 10 + (farmCount * 10);
// 工場の価格を計算する
factoryCost = 100 + (factoryCount * 100);
// クッキー円上にマウスカーソルがあれば
if (cookieCircle.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
// クッキー円が左クリックされたら
if (cookieCircle.leftClicked())
{
++cookies;
// クッキーが舞うエフェクトを追加する
effect.add<CookieEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-5, 5)), texture);
// 「+1」が上昇するエフェクトを追加する
effect.add<PlusOneEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-15, -5)), font);
}
// 背景を描く
Rect{ 0, 0, 800, 600 }.draw(Arg::top = ColorF{ 0.6, 0.5, 0.3 }, Arg::bottom = ColorF{ 0.2, 0.5, 0.3 });
// クッキーの数を整数で表示する
font(ThousandsSeparate((int32)cookies)).drawAt(60, 170, 100);
// クッキーの生産量を表示する
font(U"毎秒: {}"_fmt(cps)).drawAt(24, 170, 160);
// クッキーを描画する
texture.scaled(1.5 - springX).drawAt(cookieCircle.x, cookieCircle.y);
// エフェクトを描画する
effect.update();
// 農場ボタン
if (Button(Rect{ 340, 40, 420, 100 }, farmEmoji, font, U"クッキー農場", U"C{} / 1 CPS"_fmt(farmCost), farmCount, (farmCost <= cookies)))
{
cookies -= farmCost;
++farmCount;
}
// 工場ボタン
if (Button(Rect{ 340, 160, 420, 100 }, factoryEmoji, font, U"クッキー工場", U"C{} / 10 CPS"_fmt(factoryCost), factoryCount, (factoryCost <= cookies)))
{
cookies -= factoryCost;
++factoryCount;
}
}
}
1.4 クッキーの後光エフェクトを追加する¶
Circle::drawPie(startAngle, angle, innerColor, outerColor)
を使って後光を描きます
# include <Siv3D.hpp>
void Main()
{
while (System::Update())
{
for (int32 i = 0; i < 4; ++i)
{
double startAngle = Scene::Time() * 15_deg + i * 90_deg;
Circle{ 400, 300, 200 }.drawPie(startAngle, 60_deg, ColorF{ 1.0, 0.5 }, ColorF{ 1.0, 0.0 });
}
for (int32 i = 0; i < 6; ++i)
{
double startAngle = Scene::Time() * -15_deg + i * 60_deg;
Circle{ 400, 300, 200 }.drawPie(startAngle, 40_deg, ColorF{ 1.0, 0.5 }, ColorF{ 1.0, 0.0 });
}
}
}
# include <Siv3D.hpp>
/// @brief アイテムのボタン
/// @param rect ボタンの領域
/// @param texture ボタンの絵文字
/// @param font 文字描画に使うフォント
/// @param name アイテムの名前
/// @param desc アイテムの説明
/// @param count アイテムの所持数
/// @param enabled ボタンを押せるか
/// @return ボタンが押された場合 true, それ以外の場合は false
bool Button(const Rect& rect, const Texture& texture, const Font& font, const String& name, const String& desc, int32 count, bool enabled)
{
if (enabled)
{
rect.draw(ColorF{ 0.3, 0.5, 0.9, 0.8 });
rect.drawFrame(2, 2, ColorF{ 0.5, 0.7, 1.0 });
if (rect.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
}
else
{
rect.draw(ColorF{ 0.0, 0.4 });
rect.drawFrame(2, 2, ColorF{ 0.5 });
}
texture.scaled(0.5).drawAt(rect.x + 50, rect.y + 50);
font(name).draw(30, rect.x + 100, rect.y + 15, Palette::White);
font(desc).draw(18, rect.x + 102, rect.y + 60, Palette::White);
font(count).draw(50, Arg::rightCenter((rect.x + rect.w - 20), (rect.y + 50)), Palette::White);
return (enabled && rect.leftClicked());
}
// クッキーが舞うエフェクト
struct CookieEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 初速
Vec2 m_velocity;
// 拡大倍率
double m_scale;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_velocity{ Circular{ 80, Random(-40_deg, 40_deg) } }
, m_scale{ Random(0.2, 0.3) }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start
+ m_velocity * t + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(m_scale).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
// 「+1」が上昇するエフェクト
struct PlusOneEffect : IEffect
{
// 初期座標
Vec2 m_start;
// フォント
Font m_font;
PlusOneEffect(const Vec2& start, const Font& font)
: m_start{ start }
, m_font{ font } {}
bool update(double t) override
{
m_font(U"+1").drawAt(24, m_start.movedBy(0, t * -120), ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
void Main()
{
// クッキーの絵文字
Texture texture{ U"🍪"_emoji };
// 農場の絵文字
Texture farmEmoji{ U"🌾"_emoji };
// 工場の絵文字
Texture factoryEmoji{ U"🏭"_emoji };
// フォント
Font font{ FontMethod::MSDF, 48, Typeface::Bold };
// エフェクト
Effect effect;
// クッキーのクリック円
Circle cookieCircle{ 170, 300, 100 };
// ばねの伸び
double springX = 0.0;
// ばねの速度
double springVelocity = 0.0;
// ばねの蓄積時間
double springAccumulatedTime = 0.0;
// クッキーの個数
double cookies = 0;
// 農場の所有数
int32 farmCount = 0;
// 工場の所有数
int32 factoryCount = 0;
// 農場の価格
int32 farmCost = 10;
// 工場の価格
int32 factoryCost = 100;
// クッキーの毎秒の生産量
int32 cps = 0;
// ゲームの経過時間の蓄積
double accumulatedTime = 0.0;
while (System::Update())
{
// クッキーの毎秒の生産量を計算する
cps = (farmCount + factoryCount * 10);
// ゲームの経過時間を加算する
accumulatedTime += Scene::DeltaTime();
// 0.1 秒以上蓄積していたら
if (0.1 <= accumulatedTime)
{
accumulatedTime -= 0.1;
// 0.1 秒分のクッキー生産を加算する
cookies += (cps * 0.1);
}
// ばねの蓄積時間を加算する
springAccumulatedTime += Scene::DeltaTime();
while (0.005 <= springAccumulatedTime)
{
// ばねの力(変化を打ち消す方向)
double force = (-0.02 * springX);
// 画面を押しているときに働く力
if (cookieCircle.leftPressed())
{
force += 0.004;
}
// 速度に力を適用(減衰もさせる)
springVelocity = (springVelocity + force) * 0.92;
// 位置に反映
springX += springVelocity;
springAccumulatedTime -= 0.005;
}
// 農場の価格を計算する
farmCost = 10 + (farmCount * 10);
// 工場の価格を計算する
factoryCost = 100 + (factoryCount * 100);
// クッキー円上にマウスカーソルがあれば
if (cookieCircle.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
// クッキー円が左クリックされたら
if (cookieCircle.leftClicked())
{
++cookies;
// クッキーが舞うエフェクトを追加する
effect.add<CookieEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-5, 5)), texture);
// 「+1」が上昇するエフェクトを追加する
effect.add<PlusOneEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-15, -5)), font);
}
// 背景を描く
Rect{ 0, 0, 800, 600 }.draw(Arg::top = ColorF{ 0.6, 0.5, 0.3 }, Arg::bottom = ColorF{ 0.2, 0.5, 0.3 });
// クッキーの後光を描く
{
for (int32 i = 0; i < 4; ++i)
{
double startAngle = Scene::Time() * 15_deg + i * 90_deg;
Circle{ cookieCircle.center, 180 }.drawPie(startAngle, 60_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
}
for (int32 i = 0; i < 6; ++i)
{
double startAngle = Scene::Time() * -15_deg + i * 60_deg;
Circle{ cookieCircle.center, 180 }.drawPie(startAngle, 40_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
}
}
// クッキーの数を整数で表示する
font(ThousandsSeparate((int32)cookies)).drawAt(60, 170, 100);
// クッキーの生産量を表示する
font(U"毎秒: {}"_fmt(cps)).drawAt(24, 170, 160);
// クッキーを描画する
texture.scaled(1.5 - springX).drawAt(cookieCircle.x, cookieCircle.y);
// エフェクトを描画する
effect.update();
// 農場ボタン
if (Button(Rect{ 340, 40, 420, 100 }, farmEmoji, font, U"クッキー農場", U"C{} / 1 CPS"_fmt(farmCost), farmCount, (farmCost <= cookies)))
{
cookies -= farmCost;
++farmCount;
}
// 工場ボタン
if (Button(Rect{ 340, 160, 420, 100 }, factoryEmoji, font, U"クッキー工場", U"C{} / 10 CPS"_fmt(factoryCost), factoryCount, (factoryCost <= cookies)))
{
cookies -= factoryCost;
++factoryCount;
}
}
}
1.5 クッキーが降り注ぐ演出を付ける¶
Effect
を使って背景にクッキーを降らせます
# include <Siv3D.hpp>
// クッキーが降るエフェクト
struct CookieBackgroundEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieBackgroundEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(0.3).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t / 3.0) });
return (t < 3.0);
}
};
void Main()
{
// クッキーの絵文字
Texture texture{ U"🍪"_emoji };
// エフェクト
Effect effectBackground;
// 背景のクッキーが発生する間隔
double cookieBackgroundSpawnTime = 0.1;
// 背景のクッキーの蓄積時間
double cookieBackgroundAccumulatedTime = 0.0;
while (System::Update())
{
cookieBackgroundAccumulatedTime += Scene::DeltaTime();
while (cookieBackgroundSpawnTime <= cookieBackgroundAccumulatedTime)
{
effectBackground.add<CookieBackgroundEffect>(RandomVec2(Rect{ 0, -150, 800, 100 }), texture);
cookieBackgroundAccumulatedTime -= cookieBackgroundSpawnTime;
}
effectBackground.update();
}
}
- 描画順の都合上、クッキークリックの
Effect
とは別のEffect
を作ります
# include <Siv3D.hpp>
/// @brief アイテムのボタン
/// @param rect ボタンの領域
/// @param texture ボタンの絵文字
/// @param font 文字描画に使うフォント
/// @param name アイテムの名前
/// @param desc アイテムの説明
/// @param count アイテムの所持数
/// @param enabled ボタンを押せるか
/// @return ボタンが押された場合 true, それ以外の場合は false
bool Button(const Rect& rect, const Texture& texture, const Font& font, const String& name, const String& desc, int32 count, bool enabled)
{
if (enabled)
{
rect.draw(ColorF{ 0.3, 0.5, 0.9, 0.8 });
rect.drawFrame(2, 2, ColorF{ 0.5, 0.7, 1.0 });
if (rect.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
}
else
{
rect.draw(ColorF{ 0.0, 0.4 });
rect.drawFrame(2, 2, ColorF{ 0.5 });
}
texture.scaled(0.5).drawAt(rect.x + 50, rect.y + 50);
font(name).draw(30, rect.x + 100, rect.y + 15, Palette::White);
font(desc).draw(18, rect.x + 102, rect.y + 60, Palette::White);
font(count).draw(50, Arg::rightCenter((rect.x + rect.w - 20), (rect.y + 50)), Palette::White);
return (enabled && rect.leftClicked());
}
// クッキーが降るエフェクト
struct CookieBackgroundEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieBackgroundEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(0.3).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t / 3.0) });
return (t < 3.0);
}
};
// クッキーが舞うエフェクト
struct CookieEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 初速
Vec2 m_velocity;
// 拡大倍率
double m_scale;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_velocity{ Circular{ 80, Random(-40_deg, 40_deg) } }
, m_scale{ Random(0.2, 0.3) }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start
+ m_velocity * t + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(m_scale).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
// 「+1」が上昇するエフェクト
struct PlusOneEffect : IEffect
{
// 初期座標
Vec2 m_start;
// フォント
Font m_font;
PlusOneEffect(const Vec2& start, const Font& font)
: m_start{ start }
, m_font{ font } {}
bool update(double t) override
{
m_font(U"+1").drawAt(24, m_start.movedBy(0, t * -120), ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
void Main()
{
// クッキーの絵文字
Texture texture{ U"🍪"_emoji };
// 農場の絵文字
Texture farmEmoji{ U"🌾"_emoji };
// 工場の絵文字
Texture factoryEmoji{ U"🏭"_emoji };
// フォント
Font font{ FontMethod::MSDF, 48, Typeface::Bold };
// エフェクト
Effect effectBackground, effect;
// クッキーのクリック円
Circle cookieCircle{ 170, 300, 100 };
// ばねの伸び
double springX = 0.0;
// ばねの速度
double springVelocity = 0.0;
// ばねの蓄積時間
double springAccumulatedTime = 0.0;
// クッキーの個数
double cookies = 0;
// 農場の所有数
int32 farmCount = 0;
// 工場の所有数
int32 factoryCount = 0;
// 農場の価格
int32 farmCost = 10;
// 工場の価格
int32 factoryCost = 100;
// クッキーの毎秒の生産量
int32 cps = 0;
// ゲームの経過時間の蓄積
double accumulatedTime = 0.0;
// 背景のクッキーが発生する間隔
double cookieBackgroundSpawnTime = Math::Inf;
// 背景のクッキーの蓄積時間
double cookieBackgroundAccumulatedTime = 0.0;
while (System::Update())
{
// クッキーの毎秒の生産量を計算する
cps = (farmCount + factoryCount * 10);
// ゲームの経過時間を加算する
accumulatedTime += Scene::DeltaTime();
// 0.1 秒以上蓄積していたら
if (0.1 <= accumulatedTime)
{
accumulatedTime -= 0.1;
// 0.1 秒分のクッキー生産を加算する
cookies += (cps * 0.1);
}
// 背景のクッキー
{
// 背景のクッキーが発生する適当な間隔を cps から計算(多くなりすぎないよう緩やかに小さくなり、下限も設ける)
cookieBackgroundSpawnTime = cps ? Max(1.0 / Math::Log2(cps * 2), 0.03) : Math::Inf;
if (cps)
{
cookieBackgroundAccumulatedTime += Scene::DeltaTime();
}
while (cookieBackgroundSpawnTime <= cookieBackgroundAccumulatedTime)
{
effectBackground.add<CookieBackgroundEffect>(RandomVec2(Rect{ 0, -150, 800, 100 }), texture);
cookieBackgroundAccumulatedTime -= cookieBackgroundSpawnTime;
}
}
// ばねの蓄積時間を加算する
springAccumulatedTime += Scene::DeltaTime();
while (0.005 <= springAccumulatedTime)
{
// ばねの力(変化を打ち消す方向)
double force = (-0.02 * springX);
// 画面を押しているときに働く力
if (cookieCircle.leftPressed())
{
force += 0.004;
}
// 速度に力を適用(減衰もさせる)
springVelocity = (springVelocity + force) * 0.92;
// 位置に反映
springX += springVelocity;
springAccumulatedTime -= 0.005;
}
// 農場の価格を計算する
farmCost = 10 + (farmCount * 10);
// 工場の価格を計算する
factoryCost = 100 + (factoryCount * 100);
// クッキー円上にマウスカーソルがあれば
if (cookieCircle.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
// クッキー円が左クリックされたら
if (cookieCircle.leftClicked())
{
++cookies;
// クッキーが舞うエフェクトを追加する
effect.add<CookieEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-5, 5)), texture);
// 「+1」が上昇するエフェクトを追加する
effect.add<PlusOneEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-15, -5)), font);
// 背景のクッキーを追加する
effectBackground.add<CookieBackgroundEffect>(RandomVec2(Rect{ 0, -150, 800, 100 }), texture);
}
// 背景を描く
Rect{ 0, 0, 800, 600 }.draw(Arg::top = ColorF{ 0.6, 0.5, 0.3 }, Arg::bottom = ColorF{ 0.2, 0.5, 0.3 });
// 背景で降り注ぐクッキーを描画する
effectBackground.update();
// クッキーの後光を描く
{
for (int32 i = 0; i < 4; ++i)
{
double startAngle = Scene::Time() * 15_deg + i * 90_deg;
Circle{ cookieCircle.center, 180 }.drawPie(startAngle, 60_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
}
for (int32 i = 0; i < 6; ++i)
{
double startAngle = Scene::Time() * -15_deg + i * 60_deg;
Circle{ cookieCircle.center, 180 }.drawPie(startAngle, 40_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
}
}
// クッキーの数を整数で表示する
font(ThousandsSeparate((int32)cookies)).drawAt(60, 170, 100);
// クッキーの生産量を表示する
font(U"毎秒: {}"_fmt(cps)).drawAt(24, 170, 160);
// クッキーを描画する
texture.scaled(1.5 - springX).drawAt(cookieCircle.x, cookieCircle.y);
// エフェクトを描画する
effect.update();
// 農場ボタン
if (Button(Rect{ 340, 40, 420, 100 }, farmEmoji, font, U"クッキー農場", U"C{} / 1 CPS"_fmt(farmCost), farmCount, (farmCost <= cookies)))
{
cookies -= farmCost;
++farmCount;
}
// 工場ボタン
if (Button(Rect{ 340, 160, 420, 100 }, factoryEmoji, font, U"クッキー工場", U"C{} / 10 CPS"_fmt(factoryCost), factoryCount, (factoryCost <= cookies)))
{
cookies -= factoryCost;
++factoryCount;
}
}
}
1.6 各種データや処理をクラスや関数にまとめる¶
- 追加の処理を実装しやすくするため、各種データをクラスや関数でまとめます
# include <Siv3D.hpp>
/// @brief アイテムのボタン
/// @param rect ボタンの領域
/// @param texture ボタンの絵文字
/// @param font 文字描画に使うフォント
/// @param name アイテムの名前
/// @param desc アイテムの説明
/// @param count アイテムの所持数
/// @param enabled ボタンを押せるか
/// @return ボタンが押された場合 true, それ以外の場合は false
bool Button(const Rect& rect, const Texture& texture, const Font& font, const String& name, const String& desc, int32 count, bool enabled)
{
if (enabled)
{
rect.draw(ColorF{ 0.3, 0.5, 0.9, 0.8 });
rect.drawFrame(2, 2, ColorF{ 0.5, 0.7, 1.0 });
if (rect.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
}
else
{
rect.draw(ColorF{ 0.0, 0.4 });
rect.drawFrame(2, 2, ColorF{ 0.5 });
}
texture.scaled(0.5).drawAt(rect.x + 50, rect.y + 50);
font(name).draw(30, rect.x + 100, rect.y + 15, Palette::White);
font(desc).draw(18, rect.x + 102, rect.y + 60, Palette::White);
font(count).draw(50, Arg::rightCenter((rect.x + rect.w - 20), (rect.y + 50)), Palette::White);
return (enabled && rect.leftClicked());
}
// クッキーが降るエフェクト
struct CookieBackgroundEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieBackgroundEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(0.3).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t / 3.0) });
return (t < 3.0);
}
};
// クッキーが舞うエフェクト
struct CookieEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 初速
Vec2 m_velocity;
// 拡大倍率
double m_scale;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_velocity{ Circular{ 80, Random(-40_deg, 40_deg) } }
, m_scale{ Random(0.2, 0.3) }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start
+ m_velocity * t + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(m_scale).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
// 「+1」が上昇するエフェクト
struct PlusOneEffect : IEffect
{
// 初期座標
Vec2 m_start;
// フォント
Font m_font;
PlusOneEffect(const Vec2& start, const Font& font)
: m_start{ start }
, m_font{ font } {}
bool update(double t) override
{
m_font(U"+1").drawAt(24, m_start.movedBy(0, t * -120), ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
// アイテムのデータ
struct Item
{
// アイテムの絵文字
Texture emoji;
// アイテムの名前
String name;
// アイテムを初めて購入するときのコスト
int32 initialCost;
// アイテムの CPS
int32 cps;
// アイテムを count 個持っているときの購入コストを返す
int32 getCost(int32 count) const
{
return initialCost * (count + 1);
}
};
// クッキーのばね
class CookieSpring
{
public:
void update(double deltaTime, bool pressed)
{
// ばねの蓄積時間を加算する
m_accumulatedTime += deltaTime;
while (0.005 <= m_accumulatedTime)
{
// ばねの力(変化を打ち消す方向)
double force = (-0.02 * m_x);
// 画面を押しているときに働く力
if (pressed)
{
force += 0.004;
}
// 速度に力を適用(減衰もさせる)
m_velocity = (m_velocity + force) * 0.92;
// 位置に反映
m_x += m_velocity;
m_accumulatedTime -= 0.005;
}
}
double get() const
{
return m_x;
}
private:
// ばねの伸び
double m_x = 0.0;
// ばねの速度
double m_velocity = 0.0;
// ばねの蓄積時間
double m_accumulatedTime = 0.0;
};
// クッキーの後光を描く関数
void DrawHalo(const Vec2& center)
{
for (int32 i = 0; i < 4; ++i)
{
double startAngle = Scene::Time() * 15_deg + i * 90_deg;
Circle{ center, 180 }.drawPie(startAngle, 60_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
}
for (int32 i = 0; i < 6; ++i)
{
double startAngle = Scene::Time() * -15_deg + i * 60_deg;
Circle{ center, 180 }.drawPie(startAngle, 40_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
}
}
// アイテムの所有数をもとに CPS を計算する関数
int32 CalculateCPS(const Array<Item>& ItemTable, const Array<int32>& itemCounts)
{
int32 cps = 0;
for (size_t i = 0; i < ItemTable.size(); ++i)
{
cps += ItemTable[i].cps * itemCounts[i];
}
return cps;
}
void Main()
{
// クッキーの絵文字
const Texture texture{ U"🍪"_emoji };
// アイテムのデータ
const Array<Item> ItemTable = {
{ Texture{ U"🌾"_emoji }, U"クッキー農場", 10, 1 },
{ Texture{ U"🏭"_emoji }, U"クッキー工場", 100, 10 },
{ Texture{ U"⚓"_emoji }, U"クッキー港", 1000, 100 },
};
// 各アイテムの所有数
Array<int32> itemCounts(ItemTable.size()); // = { 0, 0, 0 }
// フォント
const Font font{ FontMethod::MSDF, 48, Typeface::Bold };
// クッキーのクリック円
const Circle CookieCircle{ 170, 300, 100 };
// エフェクト
Effect effectBackground, effect;
// クッキーのばね
CookieSpring cookieSpring;
// クッキーの個数
double cookies = 0;
// ゲームの経過時間の蓄積
double accumulatedTime = 0.0;
// 背景のクッキーの蓄積時間
double cookieBackgroundAccumulatedTime = 0.0;
while (System::Update())
{
// クッキーの毎秒の生産量を計算する
const int32 cps = CalculateCPS(ItemTable, itemCounts);
// ゲームの経過時間を加算する
accumulatedTime += Scene::DeltaTime();
// 0.1 秒以上蓄積していたら
if (0.1 <= accumulatedTime)
{
accumulatedTime -= 0.1;
// 0.1 秒分のクッキー生産を加算する
cookies += (cps * 0.1);
}
// 背景のクッキー
{
// 背景のクッキーが発生する適当な間隔を cps から計算(多くなりすぎないよう緩やかに小さくなり、下限も設ける)
const double cookieBackgroundSpawnTime = cps ? Max(1.0 / Math::Log2(cps * 2), 0.03) : Math::Inf;
if (cps)
{
cookieBackgroundAccumulatedTime += Scene::DeltaTime();
}
while (cookieBackgroundSpawnTime <= cookieBackgroundAccumulatedTime)
{
effectBackground.add<CookieBackgroundEffect>(RandomVec2(Rect{ 0, -150, 800, 100 }), texture);
cookieBackgroundAccumulatedTime -= cookieBackgroundSpawnTime;
}
}
// クッキーのばねを更新する
cookieSpring.update(Scene::DeltaTime(), CookieCircle.leftPressed());
// クッキー円上にマウスカーソルがあれば
if (CookieCircle.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
// クッキー円が左クリックされたら
if (CookieCircle.leftClicked())
{
++cookies;
// クッキーが舞うエフェクトを追加する
effect.add<CookieEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-5, 5)), texture);
// 「+1」が上昇するエフェクトを追加する
effect.add<PlusOneEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-15, -5)), font);
// 背景のクッキーを追加する
effectBackground.add<CookieBackgroundEffect>(RandomVec2(Rect{ 0, -150, 800, 100 }), texture);
}
// 背景を描く
Rect{ 0, 0, 800, 600 }.draw(Arg::top = ColorF{ 0.6, 0.5, 0.3 }, Arg::bottom = ColorF{ 0.2, 0.5, 0.3 });
// 背景で降り注ぐクッキーを描画する
effectBackground.update();
// クッキーの後光を描く
DrawHalo(CookieCircle.center);
// クッキーの数を整数で表示する
font(ThousandsSeparate((int32)cookies)).drawAt(60, 170, 100);
// クッキーの生産量を表示する
font(U"毎秒: {}"_fmt(cps)).drawAt(24, 170, 160);
// クッキーを描画する
texture.scaled(1.5 - cookieSpring.get()).drawAt(CookieCircle.center);
// エフェクトを描画する
effect.update();
for (size_t i = 0; i < ItemTable.size(); ++i)
{
// アイテムの所有数
const int32 itemCount = itemCounts[i];
// アイテムの現在の価格
const int32 itemCost = ItemTable[i].getCost(itemCount);
// アイテム 1 つあたりの CPS
const int32 itemCps = ItemTable[i].cps;
// ボタン
if (Button(Rect{ 340, (40 + 120 * i), 420, 100 }, ItemTable[i].emoji,
font, ItemTable[i].name, U"C{} / {} CPS"_fmt(itemCost, itemCps), itemCount, (itemCost <= cookies)))
{
cookies -= itemCost;
++itemCounts[i];
}
}
}
}
1.7 ゲームをセーブする¶
- シリアライズ機能を使うと簡単にセーブデータの読み書きができます
# include <Siv3D.hpp>
struct SaveData
{
double cookies;
Array<int32> itemCounts;
// シリアライズに対応させるためのメンバ関数を定義する
template <class Archive>
void SIV3D_SERIALIZE(Archive& archive)
{
archive(cookies, itemCounts);
}
};
void Main()
{
{
const SaveData saveData{ 1000, { 3, 2, 1 } };
// バイナリファイルをオープン
Serializer<BinaryWriter> writer{ U"test.save" };
if (not writer) // もしオープンに失敗したら
{
throw Error{ U"Failed to open `test.save`" };
}
// シリアライズに対応したデータを記録
writer(saveData);
}
// 読み込み先のデータ
SaveData saveData;
{
// バイナリファイルをオープン
Deserializer<BinaryReader> reader{ U"test.save" };
if (reader) // もしオープンに成功したら
{
reader(saveData);
}
}
Print << saveData.cookies;
Print << saveData.itemCounts;
while (System::Update())
{
}
}
- ゲームの開始時にセーブデータの読み込みを、終了時に書き込みを行います
# include <Siv3D.hpp>
//ゲームのセーブデータ
struct SaveData
{
double cookies;
Array<int32> itemCounts;
// シリアライズに対応させるためのメンバ関数を定義する
template <class Archive>
void SIV3D_SERIALIZE(Archive& archive)
{
archive(cookies, itemCounts);
}
};
/// @brief アイテムのボタン
/// @param rect ボタンの領域
/// @param texture ボタンの絵文字
/// @param font 文字描画に使うフォント
/// @param name アイテムの名前
/// @param desc アイテムの説明
/// @param count アイテムの所持数
/// @param enabled ボタンを押せるか
/// @return ボタンが押された場合 true, それ以外の場合は false
bool Button(const Rect& rect, const Texture& texture, const Font& font, const String& name, const String& desc, int32 count, bool enabled)
{
if (enabled)
{
rect.draw(ColorF{ 0.3, 0.5, 0.9, 0.8 });
rect.drawFrame(2, 2, ColorF{ 0.5, 0.7, 1.0 });
if (rect.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
}
else
{
rect.draw(ColorF{ 0.0, 0.4 });
rect.drawFrame(2, 2, ColorF{ 0.5 });
}
texture.scaled(0.5).drawAt(rect.x + 50, rect.y + 50);
font(name).draw(30, rect.x + 100, rect.y + 15, Palette::White);
font(desc).draw(18, rect.x + 102, rect.y + 60, Palette::White);
font(count).draw(50, Arg::rightCenter((rect.x + rect.w - 20), (rect.y + 50)), Palette::White);
return (enabled && rect.leftClicked());
}
// クッキーが降るエフェクト
struct CookieBackgroundEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieBackgroundEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(0.3).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t / 3.0) });
return (t < 3.0);
}
};
// クッキーが舞うエフェクト
struct CookieEffect : IEffect
{
// 初期座標
Vec2 m_start;
// 初速
Vec2 m_velocity;
// 拡大倍率
double m_scale;
// 回転角度
double m_angle;
// テクスチャ
Texture m_texture;
CookieEffect(const Vec2& start, const Texture& texture)
: m_start{ start }
, m_velocity{ Circular{ 80, Random(-40_deg, 40_deg) } }
, m_scale{ Random(0.2, 0.3) }
, m_angle{ Random(2_pi) }
, m_texture{ texture } {}
bool update(double t) override
{
const Vec2 pos = m_start
+ m_velocity * t + 0.5 * t * t * Vec2{ 0, 120 };
m_texture.scaled(m_scale).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
// 「+1」が上昇するエフェクト
struct PlusOneEffect : IEffect
{
// 初期座標
Vec2 m_start;
// フォント
Font m_font;
PlusOneEffect(const Vec2& start, const Font& font)
: m_start{ start }
, m_font{ font } {}
bool update(double t) override
{
m_font(U"+1").drawAt(24, m_start.movedBy(0, t * -120), ColorF{ 1.0, (1.0 - t) });
return (t < 1.0);
}
};
// アイテムのデータ
struct Item
{
// アイテムの絵文字
Texture emoji;
// アイテムの名前
String name;
// アイテムを初めて購入するときのコスト
int32 initialCost;
// アイテムの CPS
int32 cps;
// アイテムを count 個持っているときの購入コストを返す
int32 getCost(int32 count) const
{
return initialCost * (count + 1);
}
};
// クッキーのばね
class CookieSpring
{
public:
void update(double deltaTime, bool pressed)
{
// ばねの蓄積時間を加算する
m_accumulatedTime += deltaTime;
while (0.005 <= m_accumulatedTime)
{
// ばねの力(変化を打ち消す方向)
double force = (-0.02 * m_x);
// 画面を押しているときに働く力
if (pressed)
{
force += 0.004;
}
// 速度に力を適用(減衰もさせる)
m_velocity = (m_velocity + force) * 0.92;
// 位置に反映
m_x += m_velocity;
m_accumulatedTime -= 0.005;
}
}
double get() const
{
return m_x;
}
private:
// ばねの伸び
double m_x = 0.0;
// ばねの速度
double m_velocity = 0.0;
// ばねの蓄積時間
double m_accumulatedTime = 0.0;
};
// クッキーの後光を描く関数
void DrawHalo(const Vec2& center)
{
for (int32 i = 0; i < 4; ++i)
{
double startAngle = Scene::Time() * 15_deg + i * 90_deg;
Circle{ center, 180 }.drawPie(startAngle, 60_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
}
for (int32 i = 0; i < 6; ++i)
{
double startAngle = Scene::Time() * -15_deg + i * 60_deg;
Circle{ center, 180 }.drawPie(startAngle, 40_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
}
}
// アイテムの所有数をもとに CPS を計算する関数
int32 CalculateCPS(const Array<Item>& ItemTable, const Array<int32>& itemCounts)
{
int32 cps = 0;
for (size_t i = 0; i < ItemTable.size(); ++i)
{
cps += ItemTable[i].cps * itemCounts[i];
}
return cps;
}
void Main()
{
// クッキーの絵文字
const Texture texture{ U"🍪"_emoji };
// アイテムのデータ
const Array<Item> ItemTable = {
{ Texture{ U"🌾"_emoji }, U"クッキー農場", 10, 1 },
{ Texture{ U"🏭"_emoji }, U"クッキー工場", 100, 10 },
{ Texture{ U"⚓"_emoji }, U"クッキー港", 1000, 100 },
};
// 各アイテムの所有数
Array<int32> itemCounts(ItemTable.size()); // = { 0, 0, 0 }
// フォント
const Font font{ FontMethod::MSDF, 48, Typeface::Bold };
// クッキーのクリック円
const Circle CookieCircle{ 170, 300, 100 };
// エフェクト
Effect effectBackground, effect;
// クッキーのばね
CookieSpring cookieSpring;
// クッキーの個数
double cookies = 0;
// ゲームの経過時間の蓄積
double accumulatedTime = 0.0;
// 背景のクッキーの蓄積時間
double cookieBackgroundAccumulatedTime = 0.0;
// セーブデータが見つかればそれを読み込む
{
// バイナリファイルをオープン
Deserializer<BinaryReader> reader{ U"game.save" };
if (reader) // もしオープンに成功したら
{
SaveData saveData;
reader(saveData);
cookies = saveData.cookies;
itemCounts = saveData.itemCounts;
}
}
while (System::Update())
{
// クッキーの毎秒の生産量を計算する
const int32 cps = CalculateCPS(ItemTable, itemCounts);
// ゲームの経過時間を加算する
accumulatedTime += Scene::DeltaTime();
// 0.1 秒以上蓄積していたら
if (0.1 <= accumulatedTime)
{
accumulatedTime -= 0.1;
// 0.1 秒分のクッキー生産を加算する
cookies += (cps * 0.1);
}
// 背景のクッキー
{
// 背景のクッキーが発生する適当な間隔を cps から計算(多くなりすぎないよう緩やかに小さくなり、下限も設ける)
const double cookieBackgroundSpawnTime = cps ? Max(1.0 / Math::Log2(cps * 2), 0.03) : Math::Inf;
if (cps)
{
cookieBackgroundAccumulatedTime += Scene::DeltaTime();
}
while (cookieBackgroundSpawnTime <= cookieBackgroundAccumulatedTime)
{
effectBackground.add<CookieBackgroundEffect>(RandomVec2(Rect{ 0, -150, 800, 100 }), texture);
cookieBackgroundAccumulatedTime -= cookieBackgroundSpawnTime;
}
}
// クッキーのばねを更新する
cookieSpring.update(Scene::DeltaTime(), CookieCircle.leftPressed());
// クッキー円上にマウスカーソルがあれば
if (CookieCircle.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
}
// クッキー円が左クリックされたら
if (CookieCircle.leftClicked())
{
++cookies;
// クッキーが舞うエフェクトを追加する
effect.add<CookieEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-5, 5)), texture);
// 「+1」が上昇するエフェクトを追加する
effect.add<PlusOneEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-15, -5)), font);
// 背景のクッキーを追加する
effectBackground.add<CookieBackgroundEffect>(RandomVec2(Rect{ 0, -150, 800, 100 }), texture);
}
// 背景を描く
Rect{ 0, 0, 800, 600 }.draw(Arg::top = ColorF{ 0.6, 0.5, 0.3 }, Arg::bottom = ColorF{ 0.2, 0.5, 0.3 });
// 背景で降り注ぐクッキーを描画する
effectBackground.update();
// クッキーの後光を描く
DrawHalo(CookieCircle.center);
// クッキーの数を整数で表示する
font(ThousandsSeparate((int32)cookies)).drawAt(60, 170, 100);
// クッキーの生産量を表示する
font(U"毎秒: {}"_fmt(cps)).drawAt(24, 170, 160);
// クッキーを描画する
texture.scaled(1.5 - cookieSpring.get()).drawAt(CookieCircle.center);
// エフェクトを描画する
effect.update();
for (size_t i = 0; i < ItemTable.size(); ++i)
{
// アイテムの所有数
const int32 itemCount = itemCounts[i];
// アイテムの現在の価格
const int32 itemCost = ItemTable[i].getCost(itemCount);
// アイテム 1 つあたりの CPS
const int32 itemCps = ItemTable[i].cps;
// ボタン
if (Button(Rect{ 340, (40 + 120 * i), 420, 100 }, ItemTable[i].emoji,
font, ItemTable[i].name, U"C{} / {} CPS"_fmt(itemCost, itemCps), itemCount, (itemCost <= cookies)))
{
cookies -= itemCost;
++itemCounts[i];
}
}
}
// メインループの後、終了時にゲームをセーブ
{
// バイナリファイルをオープン
Serializer<BinaryWriter> writer{ U"game.save" };
// シリアライズに対応したデータを記録
writer(SaveData{ cookies, itemCounts });
}
}