コンテンツにスキップ

第 14 回(秋学期最終) | 2025-01-17

  • 第 14 回は、講義時間のすべてが課題作業時間です。
  • 最終課題やプログラミング学習について質問や相談ができます。

下記のサンプルは、最終課題のアイデアを考えるための参考になります。

1. 時間の経過を扱う

1.1 前フレームからの経過時間と、プログラム開始からの経過時間

  • プログラムの中で、時間を double 型の変数で扱います。
  • 前フレームからの経過時間を Scene::DeltaTime() で取得できます。
  • Scene::DeltaTime() は、前フレームからの経過時間(秒)を double 型で返します。
  • 前フレームからの経過時間を加算することで、プログラム開始からの経過時間を計測できます。

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// 時間を表示するためのフォント
	const Font font{ FontMethod::MSDF, 48 };

	// プログラム開始からの経過時間(秒)
	double time = 0.0;

	while (System::Update())
	{
		// 前フレームからの経過時間(秒)
		const double deltaTime = Scene::DeltaTime();

		// time に deltaTime を加算する
		time += deltaTime;

		// time を表示する
		font(U"time: {:.2f}"_fmt(time)).draw(30, Vec2{ 40, 40 }, ColorF{ 0.1 });

		// deltaTime を表示する
		font(U"deltaTime: {:.4f}"_fmt(deltaTime)).draw(30, Vec2{ 40, 100 }, ColorF{ 0.1 });
	}
}

1.2 残り時間のカウントダウンとリセット

  • 1.1 を応用して、残り時間をカウントダウンするプログラムを作成します。
  • 残り時間が 0 になったら「Time's up!」と表示します。
  • Enter キーを押すと、残り時間をリセットします。

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// フォント
	const Font font{ FontMethod::MSDF, 48 };

	// 制限時間(秒)
	const double limitTime = 8.0;

	// 残り時間(秒)
	double time = limitTime;

	while (System::Update())
	{
		// 前フレームからの経過時間(秒)
		const double deltaTime = Scene::DeltaTime();

		// 残り時間を減らす
		time -= deltaTime;

		if (0.0 < time) // 残り時間がある場合
		{
			font(U"time: {:.2f}"_fmt(time)).draw(30, Vec2{ 40, 40 }, ColorF{ 0.1 });
		}
		else // 残り時間がなくなった場合
		{
			font(U"Time's up!").draw(30, Vec2{ 40, 40 }, ColorF{ 0.1 });
			font(U"Press Enter to play again").draw(20, Vec2{ 40, 100 }, ColorF{ 0.1 });

			// Enter キーを押したら
			if (KeyEnter.down())
			{
				// 残り時間をリセットする
				time = limitTime;
			}
		}
	}
}

1.3 一定時間ごとに何かをする

  • 時間を蓄積し、一定の時間がたまったら何かをするプログラムを作成します。
  • 次のサンプルは、0.8 秒ごとにキャラクターの位置をランダムに変更します。

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// フォント
	const Font font{ FontMethod::MSDF, 48 };

	// 絵文字テクスチャ
	const Texture texture{ U"🛸"_emoji };

	// キャラクターが移動する時間間隔(秒)
	const double interval = 0.8;

	// 蓄積時間(秒)
	double accumulatedTime = 0.0;

	// キャラクターの位置
	Vec2 pos{ 320, 240 };

	while (System::Update())
	{
		// 前フレームからの経過時間(秒)
		const double deltaTime = Scene::DeltaTime();

		// 蓄積時間を増やす
		accumulatedTime += deltaTime;

		// 蓄積時間が一定の時間を超えたら
		if (interval < accumulatedTime)
		{
			// キャラクターの位置をランダムに変更する
			pos.x = Random(40, 760);
			pos.y = Random(40, 560);

			// 蓄積時間を減らす
			accumulatedTime -= interval;
		}

		// キャラクターを描画する
		texture.drawAt(pos);

		// 蓄積時間を描画する
		//font(U"accumulatedTime: {:.2f}"_fmt(accumulatedTime)).draw(20, Vec2{ 40, 40 }, ColorF{ 0.1 });
	}
}

2. 配列の基本

2.1 Array の使い方

  • Siv3D では std::vector<要素の型> を使えますが、Siv3D 用に強化された Array<要素の型> を使うとより便利です。
  • 例えば、Array は、.push_back(x); の代わりに v << x; で要素の追加ができます。
  • 配列の要素数は .size() で取得できます。
  • 単純な Array の内容は、Print で直接表示できます。

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// int32 の配列
	Array<int32> v = { 10, 20, 30 };

	while (System::Update())
	{
		ClearPrint();

		// 配列の内容を表示する
		Print << v;

		// マウスの左ボタンが押されたら
		if (MouseL.down())
		{
			// 0 以上 100 以下の乱数を追加する
			v << Random(0, 100);
		}
	}
}

2.2 配列の先頭 / 末尾の要素を削除する

  • .pop_front() で先頭の要素を削除できます。
  • .pop_back() で末尾の要素を削除できます。
  • いずれも、要素数が 0 のときに呼び出すとエラーになるため、要素数が 0 でより大きいか事前にチェックする必要があります。

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// int32 の配列
	Array<int32> v = { 10, 20, 30 };

	while (System::Update())
	{
		ClearPrint();

		// 配列の内容を表示する
		Print << v;

		// マウスの左ボタンが押されたら
		if (MouseL.down())
		{
			// 0 以上 100 以下の乱数を追加する
			v << Random(0, 100);
		}

		// [←] キーが押されたら
		if (KeyLeft.down())
		{
			// 配列が空でなければ
			if (0 < v.size())
			{
				// 先頭の要素を削除する
				v.pop_front();
			}
		}

		// [→] キーが押されたら
		if (KeyRight.down())
		{
			// 配列が空でなければ
			if (0 < v.size())
			{
				// 末尾の要素を削除する
				v.pop_back();
			}
		}
	}
}

2.3 配列の各要素にインデックスでアクセスする

  • [i] で配列の i 番目の要素にアクセスできます。
    • 第 8 回参照

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// フォント
	const Font font{ FontMethod::MSDF, 48 };

	// int32 の配列(点数の配列)
	Array<int32> v = { 300, 100, 200 };

	while (System::Update())
	{
		ClearPrint();

		// 配列の各要素にアクセスするためのループ
		for (size_t i = 0; i < v.size(); ++i)
		{
			// 配列の i 番目の要素
			int32 score = v[i];

			// 点数を描画する
			font(U"{} 点"_fmt(score)).draw(40, Vec2{ 40, (40 + 60 * i) }, ColorF{ 0.2 });
		}
	}
}

3. 配列の応用

3.1 Circle の配列

  • クラスの配列を作ることができます。
  • クリックした場所に円を追加します。
  • for (const auto& elem : v) で配列のすべての要素にアクセスするループを書けます。
    • 第 8 回、第 9 回参照

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// Circle の配列
	Array<Circle> circles;

	while (System::Update())
	{
		ClearPrint();
		Print << U"円の個数: " << circles.size();

		// マウスの左ボタンが押されたら
		if (MouseL.down())
		{
			// マウスカーソルの位置に円を追加する
			circles << Circle{ Cursor::Pos(), Random(10, 30) };
		}

		// すべての円を描画する
		for (const auto& circle : circles)
		{
			circle.draw();
		}
	}
}

3.2 配列中の Circle をすべて移動させる

  • for (auto& elem : v) で配列のすべての要素を変更するループを書けます。
    • 第 9 回参照
  • このプログラムでは、追加した円がいつまでも消えないため、3.3 のようにして、不要な円を削除する処理を追加するとよい。

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// Circle の配列
	Array<Circle> circles;

	while (System::Update())
	{
		ClearPrint();
		Print << U"円の個数: " << circles.size();

		// 前フレームからの経過時間(秒)
		const double deltaTime = Scene::DeltaTime();

		// マウスの左ボタンが押されたら
		if (MouseL.down())
		{
			// マウスカーソルの位置に円を追加する
			circles << Circle{ Cursor::Pos(), Random(10, 30) };
		}

		// すべての円を, 毎秒 50 ピクセルの速度で下に移動させる
		for (auto& circle : circles)
		{
			circle.y += (50 * deltaTime);
		}

		// すべての円を描画する
		for (const auto& circle : circles)
		{
			circle.draw();
		}
	}
}

3.3 条件を満たす要素を削除する

  • Array は、.remove_if(チェック関数) を使うことで、チェックに引っかかった要素を配列から削除します。
  • チェック関数の戻り値は bool 型、引数は配列の要素と同じ型(または const 型名&)にします。
  • 次のサンプルでは「中心の Y 座標が 500 以上の円」「右クリックされた円」をそれぞれ削除しています。

#include <Siv3D.hpp>

// 一定の座標に到達したかをチェックする関数
bool CheckFall(const Circle& circle)
{
	return (500 <= circle.y);
}

// 円が右クリックされたかをチェックする関数
bool CheckRightClicked(const Circle& circle)
{
	return circle.rightClicked();
}

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// Circle の配列
	Array<Circle> circles;

	while (System::Update())
	{
		ClearPrint();
		Print << U"円の個数: " << circles.size();

		// 前フレームからの経過時間(秒)
		const double deltaTime = Scene::DeltaTime();

		// マウスの左ボタンが押されたら
		if (MouseL.down())
		{
			// マウスカーソルの位置に円を追加する
			circles << Circle{ Cursor::Pos(), Random(10, 30) };
		}

		// すべての円を, 毎秒 50 ピクセルの速度で下に移動させる
		for (auto& circle : circles)
		{
			circle.y += (50 * deltaTime);
		}

		// 一定の座標に到達した円を削除する
		circles.remove_if(CheckFall);

		// 右クリックされた円を削除する
		circles.remove_if(CheckRightClicked);

		// すべての円を描画する
		for (const auto& circle : circles)
		{
			circle.draw();
		}
	}
}

4. 図形どうしの交差判定

4.1 図形どうしの交差

  • 図形 A と 図形 B が重なっているかは if (A.intersects(B)) で調べられます。

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// フォント
	const Font font{ FontMethod::MSDF, 48 };

	// 敵の円
	Circle enemyCircle{ 400, 200, 100 };

	while (System::Update())
	{
		// プレイヤーの円
		const Circle playerCircle{ Cursor::Pos(), 20 };

		// もし 2 つの Circle が交差している場合
		if (playerCircle.intersects(enemyCircle))
		{
			// 「交差」と表示する
			font(U"交差").draw(30, Vec2{ 20, 20 }, ColorF{ 0.1 });
		}

		// 敵の円を描く
		enemyCircle.draw(ColorF{ 0.5 });

		// プレイヤーの円を描く
		playerCircle.draw(ColorF{ 0.0, 0.6, 1.0 });
	}
}

4.2 複数の図形との交差判定

  • 複数の図形と交差判定をするサンプルです。

#include <Siv3D.hpp>

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// フォント
	const Font font{ FontMethod::MSDF, 48 };

	// 敵の円の配列
	Array<Circle> enemyCircles = {
		Circle{ 200, 200, 60 },
		Circle{ 400, 200, 60 },
		Circle{ 600, 200, 60 },
	};

	while (System::Update())
	{
		// プレイヤーの円
		const Circle playerCircle{ Cursor::Pos(), 20 };

		// 敵の各円との交差判定
		for (const auto& enemyCircle : enemyCircles)
		{
			// もし 2 つの Circle が交差している場合
			if (playerCircle.intersects(enemyCircle))
			{
				// 「交差」と表示する
				font(U"交差").draw(30, Vec2{ 20, 20 }, ColorF{ 0.1 });
			}
		}

		// 敵の各円を描く
		for (const auto& enemyCircle : enemyCircles)
		{
			enemyCircle.draw(ColorF{ 0.5 });
		}

		// プレイヤーの円を描く
		playerCircle.draw(ColorF{ 0.0, 0.6, 1.0 });
	}
}

4.3 交差した図形を削除する

  • 3.3 と組み合わせた応用です。
  • 交差した相手を、削除条件を満たす座標へ飛ばすことで、交差した図形を削除します。
  • 要素を変更する(座標を変更する)ため、const ではない for (auto& elem : v) を使います。

#include <Siv3D.hpp>

// 円が画面外に移動したかどうかを判定する関数
bool CheckRemove(const Circle& circle)
{
	return (600 < circle.y);
}

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// フォント
	const Font font{ FontMethod::MSDF, 48 };

	// 敵の円の配列
	Array<Circle> enemyCircles = {
		Circle{ 200, 200, 60 },
		Circle{ 400, 200, 60 },
		Circle{ 600, 200, 60 },
	};

	while (System::Update())
	{
		ClearPrint();
		Print << U"円の個数: " << enemyCircles.size();

		// プレイヤーの円
		const Circle playerCircle{ Cursor::Pos(), 20 };

		// 敵の各円との交差判定
		for (auto& enemyCircle : enemyCircles)
		{
			// もし 2 つの Circle が交差している場合
			if (playerCircle.intersects(enemyCircle))
			{
				// 敵の円を画面外に移動させる
				enemyCircle.y = 9999;
			}
		}

		// 画面外に移動した敵の円を削除する
		enemyCircles.remove_if(CheckRemove);

		// 敵の各円を描く
		for (const auto& enemyCircle : enemyCircles)
		{
			enemyCircle.draw(ColorF{ 0.5 });
		}

		// プレイヤーの円を描く
		playerCircle.draw(ColorF{ 0.0, 0.6, 1.0 });
	}
}

5. ゲームのシーン遷移

5.1 タイトル画面、ゲーム画面、リザルト画面

  • 現在どの画面にいるかを表現する変数を使って、タイトル画面、ゲーム画面、リザルト画面を切り替えるサンプルです。
  • 配列は .clear() で全要素を削除できます。

#include <Siv3D.hpp>

// 円が画面外に飛ばされたかどうかをチェックする関数
bool CheckRemove(const Circle& circle)
{
	return (1000 < circle.y);
}

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 1.0, 0.96, 0.92 });

	// フォント
	const Font fontBold{ FontMethod::MSDF, 48, Typeface::Bold };

	// 円が登場する時間間隔(秒)
	const double interval = 0.4;

	// 現在のシーン(0: タイトル, 1: ゲーム, 2: リザルト)
	int32 sceneID = 0;

	////////////////////////////////////////////////////////////////
	//
	// ゲームの状態

	// Circle の配列
	Array<Circle> circles;

	// ゲームの残り時間(秒)
	double time = 0.0;

	// 蓄積時間(秒)
	double accumulatedTime = 0.0;

	// スコア
	int32 score = 0;

	//
	////////////////////////////////////////////////////////////////

	while (System::Update())
	{
		ClearPrint();
		Print << U"sceneID: " << sceneID;

		if (sceneID == 0) // タイトル画面
		{
			fontBold(U"円をクリック!").drawAt(80, Vec2{ 400, 300 }, ColorF{ 0.2, 0.4, 0.6 });

			if (MouseL.down())
			{
				// ゲーム画面に移行する
				sceneID = 1;

				////////////////////////////////////////////////////////////////
				//
				//	ゲームの状態を初期化する

				// 配列の全要素を削除する
				circles.clear();

				// 残り時間を 8.0 秒に設定する
				time = 8.0;

				// 蓄積時間を interval に設定する(すぐに円を追加するため)
				accumulatedTime = interval;

				// スコアを 0 に設定する
				score = 0;

				//
				////////////////////////////////////////////////////////////////
			}
		}
		else if (sceneID == 1) // ゲーム画面
		{
			const double deltaTime = Scene::DeltaTime();

			// 蓄積時間を増やす
			accumulatedTime += deltaTime;

			// 残り時間を減らす
			time -= deltaTime;

			// 蓄積時間が一定の時間を超えたら
			if (interval < accumulatedTime)
			{
				// 円を追加する
				circles << Circle{ Random(40, 760), Random(100, 560), 30 };

				// 蓄積時間を減らす
				accumulatedTime -= interval;
			}

			// 時間切れになったら
			if (time < 0.0)
			{
				// シーンをリザルトに移行する
				sceneID = 2;
			}

			// すべての円について
			for (auto& circle : circles)
			{
				// 円がクリックされたら
				if (circle.leftClicked())
				{
					// クリックされた円を画面外に飛ばす(あとで削除される)
					circle.y = 9999;

					// スコアを加算する
					++score;
				}
			}

			// 画面外に飛ばされた円を削除する
			circles.remove_if(CheckRemove);

			// すべての円を描画する
			for (const auto& circle : circles)
			{
				circle.draw(ColorF{ 0.0, 0.6, 0.3 });
			}

			// 残り時間を表示する
			fontBold(U"残り時間: {:.1f}"_fmt(time)).drawAt(30, Vec2{ 400, 40 }, ColorF{ 0.2, 0.4, 0.6 });
		}
		else if (sceneID == 2) // リザルト画面
		{
			fontBold(U"結果: {}"_fmt(score)).drawAt(80, Vec2{ 400, 300 }, ColorF{ 0.2, 0.4, 0.6 });

			fontBold(U"エンターキーを押してください").drawAt(30, Vec2{ 400, 500 }, ColorF{ 0.2, 0.4, 0.6 });

			// エンターキーが押されたら
			if (KeyEnter.down())
			{
				// タイトル画面に移行する
				sceneID = 0;
			}
		}
	}
}

6. サンプル ① 落ちてくるアイテムを集めるゲーム

  • Left キーと Right キーで左右に移動し、落ちてくるアイテムを集めるゲームです。

コード
#include <Siv3D.hpp>

//
//	おもな変更・追加内容
//
//	1. ~を~した
//	2. ~を追加した
//	3. ~を~となるようにした
//	4. ~を~に変更した
//	5. ~を~するようにした
//	...
//

// 食べ物クラス
struct Food
{
	// 円
	Circle circle;

	// 食べ物の種類(1: キャンディー, 2: ケーキ)
	int32 type;
};

// 食べ物が画面外に出たかどうかをチェックする関数
bool CheckRemove(const Food& food)
{
	return (800 < food.circle.y);
}

void Main()
{
	// 絵文字テクスチャ
	const Texture emojiCandy{ U"🍬"_emoji };
	const Texture emojiCake{ U"🍰"_emoji };
	const Texture emojiPlayer1{ U"😃"_emoji };
	const Texture emojiPlayer2{ U"😋"_emoji };

	// フォント
	const Font fontBold{ FontMethod::MSDF, 48, Typeface::Bold };

	// 食べ物が登場する時間間隔(秒)
	const double interval = 0.8;

	// 食べ物の配列
	Array<Food> foods;

	// プレイヤー
	Circle player{ 400, 530, 30 };

	// ゲームの残り時間(秒)
	double time = 20.0;

	// 蓄積時間(秒)
	double accumulatedTime = 0.0;

	// スコア
	int32 score = 0;

	while (System::Update())
	{
		//ClearPrint();
		//Print << U"食べ物の個数: " << foods.size();

		const double deltaTime = Scene::DeltaTime();

		// 時間切れかどうか
		const bool timeUp = (time <= 0.0);

		if (!timeUp) // ゲーム
		{
			// 蓄積時間を増やす
			accumulatedTime += deltaTime;

			// 残り時間を減らす
			time -= deltaTime;

			////////////////////////////////////////////////////////////////
			//
			//	プレイヤーの移動と食べ物の取得
			//
			////////////////////////////////////////////////////////////////

			if (KeyLeft.pressed()) // [←] キーが押されている
			{
				// プレイヤーを左に移動させる
				player.x -= (400 * deltaTime);
			}
			else if (KeyRight.pressed()) // [→] キーが押されている
			{
				// プレイヤーを右に移動させる
				player.x += (400 * deltaTime);
			}

			// 各食べ物について
			for (auto& food : foods)
			{
				// プレイヤーと食べ物が重なっている場合
				if (player.intersects(food.circle))
				{
					// 食べ物を画面外に移動させる
					food.circle.y = 9999;

					// スコアを加算する
					score += 100;
				}
			}

			////////////////////////////////////////////////////////////////
			//
			//	食べ物の出現と落下
			//
			////////////////////////////////////////////////////////////////

			// 蓄積時間が一定の時間を超えたら
			if (interval < accumulatedTime)
			{
				// 食べ物を追加する
				Food food{ Circle{ Random(40, 760), -100, 30 }, Random(1, 2) };
				foods << food;

				// 蓄積時間を減らす
				accumulatedTime -= interval;
			}

			// すべての食べ物を落下させる
			for (auto& food : foods)
			{
				food.circle.y += (200 * deltaTime);
			}

			////////////////////////////////////////////////////////////////
			//
			//	食べ物の削除
			//
			////////////////////////////////////////////////////////////////

			// 画面外の食べ物を削除する
			foods.remove_if(CheckRemove);
		}
		else // 時間切れ
		{
			// 食べ物をすべて削除する
			foods.clear();
		}

		////////////////////////////////////////////////////////////////
		//
		//	描画
		//
		////////////////////////////////////////////////////////////////

		// 空を描画する
		Rect{ 0, 0, 800, 550 }.draw(Arg::top = ColorF{ 0.3, 0.6, 1.0 }, Arg::bottom = ColorF{ 0.6, 0.9, 1.0 });

		// 地面を描画する
		Rect{ 0, 550, 800, 50 }.draw(ColorF{ 0.3, 0.6, 0.3 });

		// 食べ物を描画する
		for (const auto& food : foods)
		{
			if (food.type == 1) // キャンディー
			{
				emojiCandy.scaled(0.5).drawAt(food.circle.center);
			}
			else // ケーキ
			{
				emojiCake.scaled(0.5).drawAt(food.circle.center);
			}

			// 当たり判定を描画する
			//food.circle.drawFrame(3, Palette::Red);
		}

		// プレイヤーを描画する
		if (!timeUp) // ゲーム
		{
			emojiPlayer1.scaled(0.5).drawAt(player.center);

			// プレイヤーの当たり判定を描画する
			//player.drawFrame(3, Palette::Red);

			// 残り時間を表示する
			fontBold(U"残り時間: {:.1f}"_fmt(time)).draw(24, Vec2{ 400, 20 }, ColorF{ 1.0 });
		}
		else // 時間切れ
		{
			emojiPlayer2.scaled(0.5).drawAt(player.center);
		}

		// スコアを表示する
		fontBold(U"{}"_fmt(score)).draw(24, Arg::topRight = Vec2{ 780, 20 }, ColorF{ 1.0 });
	}
}

7. サンプル ② シューティングゲーム

  • Left キーと Right キーで左右に移動し、敵にビームを当てて倒すゲームです。
  • 敵の状態は Enemy クラスで管理します。
  • このコードにはスコアや残り時間、プレイヤーの HP などの要素は含まれていません。

コード
#include <Siv3D.hpp>

//
//	おもな変更・追加内容
//
//	1. ~を~した
//	2. ~を追加した
//	3. ~を~となるようにした
//	4. ~を~に変更した
//	5. ~を~するようにした
//	...
//

// 敵クラス
struct Enemy
{
	// 円
	Circle circle;

	// HP(0 になると破壊)
	double hp;
};

// 敵が画面外に出たかどうかを判定する関数
bool CheckRemove(const Enemy& enemy)
{
	return (800 < enemy.circle.y);
}

// 敵の HP が 0 以下かどうかを判定する関数
bool CheckHP(const Enemy& enemy)
{
	return (enemy.hp <= 0.0);
}

void Main()
{
	// 背景色を設定する
	Scene::SetBackground(ColorF{ 0.3, 0.7, 1.0 });

	// 絵文字テクスチャ
	const Texture emojiEnemy{ U"👻"_emoji };
	const Texture emojiPlayer{ U"🤖"_emoji };

	// フォント
	const Font fontBold{ FontMethod::MSDF, 48, Typeface::Bold };

	// 敵が登場する時間間隔(秒)
	const double interval = 0.8;

	// 敵の配列
	Array<Enemy> enemies;

	// プレイヤー
	Circle player{ 400, 530, 30 };

	// 蓄積時間(秒)
	double accumulatedTime = 0.0;

	while (System::Update())
	{
		//ClearPrint();
		//Print << U"敵の数: " << enemies.size();

		const double deltaTime = Scene::DeltaTime();

		////////////////////////////////////////////////////////////////
		//
		//	プレイヤーの移動
		//
		////////////////////////////////////////////////////////////////

		if (KeyLeft.pressed()) // [←] キーが押されている
		{
			// プレイヤーを左に移動させる
			player.x -= (400 * deltaTime);
		}
		else if (KeyRight.pressed()) // [→] キーが押されている
		{
			// プレイヤーを右に移動させる
			player.x += (400 * deltaTime);
		}

		// プレイヤーが画面外に行かないようにする
		if (player.x < 40)
		{
			player.x = 40;
		}
		else if (760 < player.x)
		{
			player.x = 760;
		}

		////////////////////////////////////////////////////////////////
		//
		//	敵の出現と移動
		//
		////////////////////////////////////////////////////////////////

		// 蓄積時間を増やす
		accumulatedTime += deltaTime;

		// 蓄積時間が一定の時間を超えたら
		if (interval < accumulatedTime)
		{
			// 敵を追加する
			enemies << Enemy{ { Random(40, 760), -50, 20 }, 0.5 };

			// 蓄積時間を減らす
			accumulatedTime -= interval;
		}

		// すべての敵を下に移動させる
		for (auto& enemy : enemies)
		{
			// 敵を下に移動させる
			enemy.circle.y += (200 * deltaTime);
		}

		////////////////////////////////////////////////////////////////
		//
		//	プレイヤーの攻撃(ビーム)
		//
		////////////////////////////////////////////////////////////////

		// ビームを表現する長方形
		const RectF beamRect{ (player.x - 5), 0, 10, 500 };

		// 各敵について
		for (auto& enemy : enemies)
		{
			// ビームがあたっていたら
			if (beamRect.intersects(enemy.circle))
			{
				// HP を削る
				enemy.hp -= deltaTime;

				if (enemy.hp <= 0.0)
				{
					// 倒したときの処理をここに書ける
				}
			}
		}

		////////////////////////////////////////////////////////////////
		//
		//	敵の削除
		//
		////////////////////////////////////////////////////////////////

		// 画面外の敵を削除する
		// ヒント: この前後で enemies.size() を比べると、倒せずに画面外に出た敵の数がわかる
		enemies.remove_if(CheckRemove);

		// HP が 0.0 以下の敵を削除する
		enemies.remove_if(CheckHP);

		////////////////////////////////////////////////////////////////
		//
		//	描画
		//
		////////////////////////////////////////////////////////////////

		// 敵を描画する
		for (const auto& enemy : enemies)
		{
			if (enemy.circle.intersects(beamRect)) // ビームがあたっていたら
			{
				// 大きさにランダム性を持たせる
				emojiEnemy.scaled(0.5 + Random(0.0, 0.15)).drawAt(enemy.circle.center);
			}
			else
			{
				emojiEnemy.scaled(0.5).drawAt(enemy.circle.center);
			}
		}

		// プレイヤーを描画する
		emojiPlayer.scaled(0.5).drawAt(player.center);

		// ビームを描画する
		beamRect.draw(ColorF{ 1.0, 0.8, 0.6 });
	}
}

8. Siv3D の公式チュートリアル

最終課題・後半

📝 提出課題(選択)

ここまでの学習の振り返りとして、Siv3D を用いて、次の 3 つから 1 つを選択してプログラム作成に取り組んでください。

  1. 「第 14 回サンプル ① 落ちてくるアイテムを集めるゲーム」をアレンジ・発展させる
  2. 「第 14 回サンプル ② シューティングゲーム」をアレンジ・発展させる
  3. 自由制作

掲載サンプルや Web で見つかるサンプルを多少変更しただけのものは低い評価になります。何らかの新しい要素や工夫を加え、オリジナリティを出すようにしてください。

コードの先頭に、どのような発展を加えたかを簡単にコメントで記入してください。


  • 自作した画像やテキストファイルの読み込みなど、外部ファイルの利用はできません。Siv3D 標準搭載の絵文字や C++ のコードのみで実装してください。
  • コードまたは コードの共有 URL(Wandbox)を提出してください。

🍎 絵文字を含むコードは Wandbox を使う 🚙

Moodle の仕様により、コード記入欄に絵文字が含まれると提出がエラーになることがあります。その場合は Wandbox でコードを書いて、コードの共有 URL(Wandbox) で提出してください。

Wandbox は Siv3D に対応していないため、Wandbox 上では Siv3D のプログラムはコンパイルエラーになりますが、気にする必要はありません。