OpenSiv3D | 落ちてくる食べ物を集めるゲームを作る Day 1

8 min read読了の目安(約7900字

このコースでは、初歩的な C++ の文法と OpenSiv3D の機能を使って、落ちてくる食べ物を集めるシンプルなゲームを作ります。

1. 絵文字の表示


画面にプレイヤーを表示しましょう。

画面に画像を描きたいときは Texture を作成し、.draw() または .drawAt() します。.drawAt() では、テクスチャの中心をどこに据えるかをシーンの座標で指定します。

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

void Main()
{
	// プレイヤーのテクスチャ
	const Texture playerTexture{ Emoji{ U"😋" } };

	while (System::Update())
	{
		// プレイヤーを描く
		playerTexture.drawAt(400, 500);
	}
}

2. 地面を作る


地面を描きましょう。

Rect{ x, y, w, h }.draw(color) で、左上の座標が (x, y), 幅が w, 高さが h の長方形を色 color で描けます。色の表現には ColorF を使います。ColorF では、0.0 - 1.0 の範囲で RGB の各成分を指定します。

見えているシーンの基本サイズは幅が 800, 高さが 600 です。それを考慮して長方形の場所と大きさを決めましょう。

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

void Main()
{
	// プレイヤーのテクスチャ
	const Texture playerTexture{ Emoji{ U"😋" } };

	while (System::Update())
	{
		// 地面を描く
		Rect{ 0, 520, 800, 80 }.draw(ColorF{ 0.2, 0.6, 0.3 });

		// プレイヤーを描く
		playerTexture.drawAt(400, 500);
	}
}

3. 空を作る


空を描きましょう。

Rect.draw(Arg::top = c1, Arg::bottom = c2) で、上から下に色 c1 → c2 のグラデーションをつけることができます。お手本からアレンジして夕焼けや夜空にしてみるのもよいでしょう。

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

void Main()
{
	// プレイヤーのテクスチャ
	const Texture playerTexture{ Emoji{ U"😋" } };

	while (System::Update())
	{
		// 空を描く(グラデーション)
		Rect{ 0, 0, 800, 520 }
			.draw(Arg::top = ColorF{ 0.1, 0.3, 0.6 }, Arg::bottom = ColorF{ 0.3, 0.7, 1.0 });

		// 地面を描く
		Rect{ 0, 520, 800, 80 }.draw(ColorF{ 0.2, 0.6, 0.3 });

		// プレイヤーを描く
		playerTexture.drawAt(400, 500);
	}
}

4. プレイヤーの移動


矢印キーを使ってプレイヤーが左右に移動できるようにしましょう。

プレイヤーの位置は Vec2 型で表現できます。Vec2 型は double 型のメンバ変数 x, y を持ちます。キーボードが押されているかを判定するには if (Key***.pressed()) を使います。*** にはキーの名前が入ります。左矢印キーの場合は KeyLeft, 右矢印キーの場合は KeyRight です。

キーを押すことで毎秒何ピクセル横に移動するかスピードを決め、Scene::DeltaTime() で得られる、フレームの経過時間 (60FPS の場合約 0.016 秒) をかけた量だけ左右に移動します。

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

void Main()
{
	// プレイヤーのテクスチャ
	const Texture playerTexture{ Emoji{ U"😋" } };

	// プレイヤーが毎秒何ピクセルの速さで移動するか
	double playerSpeed = 300.0;

	// プレイヤーの位置
	Vec2 playerPos{ 400, 500 };

	while (System::Update())
	{
		if (KeyLeft.pressed()) // 左矢印キーが押されていたら
		{
			playerPos.x -= (playerSpeed * Scene::DeltaTime());
		}
		else if (KeyRight.pressed()) // 右矢印キーが押されていたら
		{
			playerPos.x += (playerSpeed * Scene::DeltaTime());
		}

		// 空を描く(グラデーション)
		Rect{ 0, 0, 800, 520 }
			.draw(Arg::top = ColorF{ 0.1, 0.3, 0.6 }, Arg::bottom = ColorF{ 0.3, 0.7, 1.0 });

		// 地面を描く
		Rect{ 0, 520, 800, 80 }.draw(ColorF{ 0.2, 0.6, 0.3 });

		// プレイヤーを描く
		playerTexture.drawAt(playerPos);
	}
}

5. プレイヤーが画面外に出ないようにする


プレイヤーが画面外に出ないように値を制限します。

Clamp(value, min. max) は値 valuemin 以上 max 以下に収めた値を返します。value, min, max は 3 つとも同じ型である必要があります。(同じ型でない場合 Clamp<double>(value, min, max) のように <> で明示的に型を指定します)

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

void Main()
{
	// プレイヤーのテクスチャ
	const Texture playerTexture{ Emoji{ U"😋" } };

	// プレイヤーが毎秒何ピクセルの速さで移動するか
	double playerSpeed = 300.0;

	// プレイヤーの位置
	Vec2 playerPos{ 400, 500 };

	while (System::Update())
	{
		if (KeyLeft.pressed()) // 左矢印キーが押されていたら
		{
			playerPos.x -= (playerSpeed * Scene::DeltaTime());
		}
		else if (KeyRight.pressed()) // 右矢印キーが押されていたら
		{
			playerPos.x += (playerSpeed * Scene::DeltaTime());
		}

		// playerPos.x を 50 以上、750 以下に収める
		playerPos.x = Clamp(playerPos.x, 50.0, 750.0);

		// 空を描く(グラデーション)
		Rect{ 0, 0, 800, 520 }
			.draw(Arg::top = ColorF{ 0.1, 0.3, 0.6 }, Arg::bottom = ColorF{ 0.3, 0.7, 1.0 });

		// 地面を描く
		Rect{ 0, 520, 800, 80 }.draw(ColorF{ 0.2, 0.6, 0.3 });

		// プレイヤーを描く
		playerTexture.drawAt(playerPos);
	}
}

6. 落ちてくる食べ物の表示


画面に食べ物を登場させましょう。

食べ物の情報を表す Food クラスを struct で作ります。メンバ変数は食べ物の座標 pos です。

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

// 食べ物クラス
struct Food
{
	Vec2 pos;
};

void Main()
{
	// プレイヤーのテクスチャ
	const Texture playerTexture{ Emoji{ U"😋" } };

	// 食べ物のテクスチャ
	const Texture foodTexture{ Emoji{ U"🍰" } };

	// プレイヤーが毎秒何ピクセルの速さで移動するか
	double playerSpeed = 300.0;

	// プレイヤーの位置
	Vec2 playerPos{ 400, 500 };

	// 食べ物
	Food food{ .pos = Vec2{ 300, 100 } };

	while (System::Update())
	{
		if (KeyLeft.pressed()) // 左矢印キーが押されていたら
		{
			playerPos.x -= (playerSpeed * Scene::DeltaTime());
		}
		else if (KeyRight.pressed()) // 右矢印キーが押されていたら
		{
			playerPos.x += (playerSpeed * Scene::DeltaTime());
		}

		// playerPos.x を 50 以上、750 以下に収める
		playerPos.x = Clamp(playerPos.x, 50.0, 750.0);

		// 空を描く(グラデーション)
		Rect{ 0, 0, 800, 520 }
			.draw(Arg::top = ColorF{ 0.1, 0.3, 0.6 }, Arg::bottom = ColorF{ 0.3, 0.7, 1.0 });

		// 地面を描く
		Rect{ 0, 520, 800, 80 }.draw(ColorF{ 0.2, 0.6, 0.3 });

		// プレイヤーを描く
		playerTexture.drawAt(playerPos);

		// 食べ物を描く
		foodTexture.drawAt(food.pos);
	}
}

7 食べ物の大きさを変更する


食べ物が大きすぎるので、テクスチャを縮小して描きましょう。

Texture は、.scaled(s) によって s 倍に拡大縮小して描くことができます。1.0 より小さくすれば元の画像よりも小さく表示されます。

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

// 食べ物クラス
struct Food
{
	Vec2 pos;
};

void Main()
{
	// プレイヤーのテクスチャ
	const Texture playerTexture{ Emoji{ U"😋" } };

	// 食べ物のテクスチャ
	const Texture foodTexture{ Emoji{ U"🍰" } };

	// プレイヤーが毎秒何ピクセルの速さで移動するか
	double playerSpeed = 300.0;

	// プレイヤーの位置
	Vec2 playerPos{ 400, 500 };

	// 食べ物
	Food food{ .pos = Vec2{ 300, 100 } };

	while (System::Update())
	{
		if (KeyLeft.pressed()) // 左矢印キーが押されていたら
		{
			playerPos.x -= (playerSpeed * Scene::DeltaTime());
		}
		else if (KeyRight.pressed()) // 右矢印キーが押されていたら
		{
			playerPos.x += (playerSpeed * Scene::DeltaTime());
		}

		// playerPos.x を 50 以上、750 以下に収める
		playerPos.x = Clamp(playerPos.x, 50.0, 750.0);

		// 空を描く(グラデーション)
		Rect{ 0, 0, 800, 520 }
			.draw(Arg::top = ColorF{ 0.1, 0.3, 0.6 }, Arg::bottom = ColorF{ 0.3, 0.7, 1.0 });

		// 地面を描く
		Rect{ 0, 520, 800, 80 }.draw(ColorF{ 0.2, 0.6, 0.3 });

		// プレイヤーを描く
		playerTexture.drawAt(playerPos);

		// 食べ物を描く(サイズは 0.5 倍)
		foodTexture.scaled(0.5).drawAt(food.pos);
	}
}

8. 食べ物をたくさん用意する


配列を使って食べ物を複数用意しましょう。

Array<Food>Food 型の要素をたくさん持つ配列を作れます。配列に要素を追加するときは << を使います。

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

// 食べ物クラス
struct Food
{
	Vec2 pos;
};

void Main()
{
	// プレイヤーのテクスチャ
	const Texture playerTexture{ Emoji{ U"😋" } };

	// 食べ物のテクスチャ
	const Texture foodTexture{ Emoji{ U"🍰" } };

	// プレイヤーが毎秒何ピクセルの速さで移動するか
	double playerSpeed = 300.0;

	// プレイヤーの位置
	Vec2 playerPos{ 400, 500 };

	// 食べ物
	Array<Food> foods;
	foods << Food{ .pos = Vec2{ 300, 100 } };
	foods << Food{ .pos = Vec2{ 500, 200 } };
	foods << Food{ .pos = Vec2{ 700, 300 } };

	while (System::Update())
	{
		if (KeyLeft.pressed()) // 左矢印キーが押されていたら
		{
			playerPos.x -= (playerSpeed * Scene::DeltaTime());
		}
		else if (KeyRight.pressed()) // 右矢印キーが押されていたら
		{
			playerPos.x += (playerSpeed * Scene::DeltaTime());
		}

		// playerPos.x を 50 以上、750 以下に収める
		playerPos.x = Clamp(playerPos.x, 50.0, 750.0);

		// 空を描く(グラデーション)
		Rect{ 0, 0, 800, 520 }
			.draw(Arg::top = ColorF{ 0.1, 0.3, 0.6 }, Arg::bottom = ColorF{ 0.3, 0.7, 1.0 });

		// 地面を描く
		Rect{ 0, 520, 800, 80 }.draw(ColorF{ 0.2, 0.6, 0.3 });

		// プレイヤーを描く
		playerTexture.drawAt(playerPos);

		for (const auto& food : foods)
		{
			// 食べ物を描く(サイズは 0.5 倍)
			foodTexture.scaled(0.5).drawAt(food.pos);
		}
	}
}

▶ Day 2 に続く