LoginSignup
0
0

四則演算を使ったゲームを作った

Posted at

はじめに

お正月に↓の記事を読んだ。

“どこかで見た広告”風数式パズルゲーム『Formula Warriors』ブラウザ向けに無料公開。二択の数式を選び取り、正しいルートを突き進む

自分で作ってみるのも楽しそう、ということで作ってみた。
最近お気に入りのSiv3Dというフレームワークを使って作った。
楽しかった。

作ったもの

効果音:Springin’ Sound Stock
音楽:魔王魂
画像:ぴぽや https://pipoya.net/

画面の説明

プレイヤーは猫たち。
基本的には近くの敵を追跡して交戦する。マウスでクリックするとそこに集合させることも出来る。
上のバーはHPの合計。
四則演算の結果に従って猫が増減する(□が猫の数)。

ソースコード

# include <Siv3D.hpp> // Siv3D v0.6.13
#include <random>
#include <vector>
#include <algorithm>
#include <string>
using namespace s3d;
using namespace std;
const int width = 600;
const int height = 600;
const int h = 100;
const Vec2 npos(-1, -1);

int sign(int i)
{
	if (i > 0) return 1;
	else if (i < 0) return -1;
	else return 0;
}

class Mob
{
private:
	Texture texture;
	int frame;
	int df;
	Font font{ 20 };
	bool player;

	void move(const Vec2& dpos) // dposだけ移動する
	{
		pos += dpos;
	}
	void move_to(const Vec2& pos)
	{
		Vec2 direction(pos - this->pos);
		direction.normalize();
		move(velocity * direction);
	}

	void get_nearest_enemy(const std::vector<Mob>& enemy, int& m_min, double& d_min)
	{
		int M = enemy.size();
		d_min = 1e15;
		for (int m = 0; m < M; m++)
		{
			double d = Geometry2D::Distance(this->pos, enemy[m].pos);
			if (d < d_min && d>0)
			{
				d_min = d;
				m_min = m;
			}
		}
	}
	
public:
	int HP;
	Vec2 pos;
	double velocity;
	int max_damege;
	
	Mob(const char32_t* path,  const Vec2 &pos, int HP=100, double velocity=1, int max_damege=10) : texture(path), frame{ 0 }, df{ 1 }, HP(HP), pos(pos), velocity(velocity), max_damege(max_damege)
	{
	}

	void act(std::vector<Mob>& enemy, Vec2 pos=npos)
	{
		int m_min;
		double d_min;
		get_nearest_enemy(enemy, m_min, d_min);
		if (d_min < 20) // 近接
		{
			int damege = Random(max_damege);
			font(damege).drawAt(enemy[m_min].pos + Vec2(0, -30), ColorF{ 1.0, 0.0, 0.0 });
			enemy[m_min].HP -= damege;
		}

		if (d_min > 10 && pos==npos)
		{
			move_to(enemy[m_min].pos);
		}
		else
		{
			move_to(pos);
		}
	}

	void draw()
	{
		double u = 1. / 3. * frame;
		double v = 0;
		double w = 1. / 3.;
		double h = 1. / 4.;
		texture.uv(u, v, w, h).drawAt(pos);
		frame += df;
		if (frame == 2 || frame == 0) df *= -1;
	}
};

s3d::String opstring(int optype)
{
	s3d::String ret;
	if (optype == 0) ret = U"+";
	else if (optype == 1) ret = U"-";
	else if (optype == 2) ret = U"×";
	else ret = U"÷";

	return ret;
}

double op(double a, double b, int optype)
{
	double ret;
	if (optype == 0)
	{
		ret = a + b;
	}
	else if (optype == 1)
	{
		ret = a - b;
	}
	else if (optype == 2)
	{
		ret = a * b;
	}
	else
	{
		ret = a / b;
	}
	return ret;
}

void Main()
{
	Window::Resize(width, height + h);
	const Texture background_op{U"img/op.jpg"};
	RectF rect_background_op = background_op.region(Point(0, 0));
	

	while (!rect_background_op.leftClicked())
	{
		background_op.draw(Point(0, 0));
		System::Update();
		System::Sleep(0.01s);
	}

	const Audio audio{ U"bgm/battle.mp3" };
	audio.play();
	const Audio audio_button{ U"bgm/button.mp3" };
	const Texture background{ U"img/pipo-battlebg001.jpg" };
	RectF rect_background = background.region(Point(0, h));

	int M = 15, M_enemy = 80;
	std::vector<Mob> neko;
	std::vector<Mob> enemy;
	for (int m = 0; m < M; m++)
	{
		 neko.push_back(Mob(U"img/neko.png",  RandomVec2(rect_background)));
	}
	for (int m = 0; m < M_enemy; m++)
	{
		enemy.push_back(Mob(U"img/enemy.png", RandomVec2(rect_background)));
	}

	Font font(30, Typeface::Bold);
	int t = 0;
	s3d::String str_op[2];
	bool disp_op = false;
	bool OP = false;
	double num[2];

	Vec2 pos_op[2];
	pos_op[0] = Vec2(10, h / 2);
	pos_op[1] = Vec2(200, h / 2);
	int op_type[2];
	ColorF op_color[2];
	for (int opp = 0; opp < 2; opp++)
	{
		op_color[opp] = ColorF{ 0,0,0 };
	}
	Font sfont(15);
	int Top = 10; // 計算を表示するタイミング
	int DT = 30; // 計算を表示している時間

	while (System::Update())
	{
		background.draw(Vec2(0,h));
		M = neko.size();

		// 演算の表示
		RectF rect_op[2];
		for (int opp = 0; opp < 2; opp++)
		{
			rect_op[opp] = font(str_op[opp]).region(pos_op[opp]);
			rect_op[opp].draw().drawFrame(1, 1, Palette::Orange);; // 演算文字列がないときは表示されない(サイズゼロなので)
		}

		// 演算の実行
		for (int opp = 0; opp < 2; opp++)
		{
			if (disp_op && rect_op[opp].leftClicked() & !OP)
			{
				audio_button.play();
				op_color[opp] = ColorF{ 1,0,0 };
				OP = true;
				M = op(M, num[opp], op_type[opp]);
				if (M <= 1) M = 1;
				int nekosize = neko.size();
				if (M >= neko.size())
				{
					for (int m = 0; m < M - nekosize; m++)
					{
						neko.push_back(Mob(U"img/neko.png", RandomVec2(rect_background)));
					}
				}
				else
				{
					for (int m = 0; m < nekosize - M; m++)
					{
						neko.pop_back();
					}
				}
			}
			font(str_op[opp]).draw(pos_op[opp], op_color[opp]);
		}

		if (t == Top)
		{
			double test = 0;
			while (test < 1) // 2択のどちらかは得をするように
			{
				for (int opp = 0; opp < 2; opp++) // 2択の生成
				{
					num[opp] = Random(0.1, 5.0);
					op_type[opp] = Random(0, 3);
					str_op[opp] = U"□ " + opstring(op_type[opp]) + U" {:.1f}"_fmt(num[opp]);
				}
				test = std::max<double>(op(1, num[0], op_type[0]), op(1, num[1], op_type[1]));
			}
			disp_op = true;
		}
		if (t == Top+DT) // 演算を削除(タイムオーバー)
		{
			for (int opp = 0; opp < 2; opp++)
			{
				op_color[opp] = ColorF{ 0,0,0 };
				str_op[opp] = U"";
			}
			t = 0;
			disp_op = false;
			OP = false;
		}

		int HPsum = 0;
		for (auto& e : neko)
		{
			if (rect_background.leftPressed()) // クリックしているところに向かう
			{
				e.act(enemy, Cursor::PosF());
			}
			else // 近くの敵に向かう
			{
				e.act(enemy);
			}
			e.draw();
			HPsum += (e.HP < 0 ? 0 : e.HP);
		}
		int HPsume = 0;
		for (auto& e : enemy)
		{
			e.act(neko);
			e.draw();
			HPsume += (e.HP<0 ? 0 : e.HP);
		}
		Rect(10, 10, HPsum/10, 10).draw(Palette::Blue);
		Rect(10, 30, HPsume/10, 10).draw(Palette::Red);
		sfont(U"cat: {}"_fmt(neko.size())).draw(Vec2(0, 8));

		// 死亡判定
		neko.erase(std::remove_if(neko.begin(), neko.end(), [&](const Mob& mob) {return (mob.HP < 0); }), neko.end());
		enemy.erase(std::remove_if(enemy.begin(), enemy.end(), [&](const Mob& mob) {return (mob.HP < 0); }), enemy.end());

		if (neko.size() == 0 || enemy.size() == 0)
		{
			System::Sleep(1s);
			break;
		}
		System::Sleep(0.1s);

		t += 1;
	}

	const Texture background_hc(U"img/highsccore.jpg");
	font = Font(40);
	while(System::Update())
	{
		background_hc.draw();
		
		font(U"猫の数:{}"_fmt(neko.size())).draw(Point(10, height / 2));
		if (MouseL.pressed()) break;
		System::Sleep(0.1s);
	}
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0