🚪

OpenSiv3D | 脱出ゲームを作る Day 2

15 min read

このコースでは、初歩的な C++ の文法と OpenSiv3D の機能を使って、シンプルな脱出ゲームを作ります。

8. クラスを作る


アイテムに関連する情報をクラスにまとめて管理しましょう。

Item クラスを作り、private なメンバ変数に Texture m_textureCircle m_circle を、public なメンバ関数として、初期化のためのコンストラクタと、描画のためのメンバ関数 .draw() を用意します。

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

class Item
{
public: // クラスの外からアクセスできる

	// クラスの初期化のためのメンバ関数(コンストラクタ)
	Item(const Emoji& emoji, const Circle& circle)
		: m_texture{ emoji }
		, m_circle{ circle }
	{
		// 特に何もしない
	}

	// 描画のためのメンバ関数
	void draw() const
	{
		m_circle.draw(ColorF{ 1.0, 0.5, 0.0, 0.2 });
		m_texture.drawAt(m_circle.center);
	}

private: // クラスの外からはアクセスさせない

	Texture m_texture;

	Circle m_circle;
};

void Main()
{
	Scene::SetBackground(ColorF{ 0.9, 0.85, 0.8 });
	
	Item ticket{ Emoji{ U"🎫" }, Circle{ 200, 300, 80 } };

	while (System::Update())
	{
		ticket.draw();
	}
}

9. クラスを拡張する


脱出ゲームの開発を便利にするメンバ関数 .updateCursorStyle().clicked() を追加します。

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

class Item
{
public: // クラスの外からアクセスできる

	// クラスの初期化のためのメンバ関数(コンストラクタ)
	Item(const Emoji& emoji, const Circle& circle)
		: m_texture{ emoji }
		, m_circle{ circle }
	{
		// 特に何もしない
	}

	// カーソルスタイルの更新のためのメンバ関数
	void updateCursorStyle() const
	{
		if (m_circle.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hand);
		}
	}

	// クリックされたかを返すメンバ関数
	bool clicked() const
	{
		return m_circle.leftClicked();
	}

	// 描画のためのメンバ関数
	void draw() const
	{
		m_circle.draw(ColorF{ 1.0, 0.5, 0.0, 0.2 });
		m_texture.drawAt(m_circle.center);
	}

private: // クラスの外からはアクセスさせない

	Texture m_texture;

	Circle m_circle;
};

void Main()
{
	Scene::SetBackground(ColorF{ 0.9, 0.85, 0.8 });
	
	Item ticket{ Emoji{ U"🎫" }, Circle{ 200, 300, 80 } };

	while (System::Update())
	{
		ticket.updateCursorStyle();

		if (ticket.clicked())
		{
			ClearPrint();
			Print << U"映画のチケットだ。";
		}

		ticket.draw();
	}
}

10. シナリオを作る


Item クラスを作ったことで、これまでより短いプログラムでストーリーを作れるようになりました。練習として自分で 3 パターンのストーリーを作ってみましょう。

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

class Item
{
public: // クラスの外からアクセスできる

	// クラスの初期化のためのメンバ関数(コンストラクタ)
	Item(const Emoji& emoji, const Circle& circle)
		: m_texture{ emoji }
		, m_circle{ circle }
	{
		// 特に何もしない
	}

	// カーソルスタイルの更新のためのメンバ関数
	void updateCursorStyle() const
	{
		if (m_circle.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hand);
		}
	}

	// クリックされたかを返すメンバ関数
	bool clicked() const
	{
		return m_circle.leftClicked();
	}

	// 描画のためのメンバ関数
	void draw() const
	{
		m_circle.draw(ColorF{ 1.0, 0.5, 0.0, 0.2 });
		m_texture.drawAt(m_circle.center);
	}

private: // クラスの外からはアクセスさせない

	Texture m_texture;

	Circle m_circle;
};

void Main()
{
	Item ticket{ Emoji{ U"🎫" }, Circle{ 200, 300, 80 } };
	Item flashlight{ Emoji{ U"🔦" }, Circle{ 600, 300, 80 } };

	// 明かりが付いているか
	bool lightOn = false;

	while (System::Update())
	{
		if (lightOn)
		{
			// 明るい背景
			Scene::SetBackground(ColorF{ 0.9, 0.85, 0.8 });
		}
		else
		{
			// 暗い背景
			Scene::SetBackground(ColorF{ 0.3, 0.3, 0.3 });
		}

		ticket.updateCursorStyle();
		flashlight.updateCursorStyle();

		if (ticket.clicked())
		{
			ClearPrint();
			
			if (lightOn)
			{
				Print << U"映画のチケットだ。";
			}
			else
			{
				Print << U"何かの紙切れだ。暗くてよく読めない。";
			}
		}

		if (flashlight.clicked())
		{
			ClearPrint();
			Print << U"懐中電灯の明かりを付けた。";
			lightOn = true;
		}

		ticket.draw();
		flashlight.draw();
	}
}

11. アイテムを非表示にする


アイテムの表示・非表示を切り替えられるようにしましょう。

アイテムが見えているか(アクションが発生するか)を決める bool m_visible というメンバ変数を追加し、それに合わせてコンストラクタやメンバ関数の処理を変更します。m_visible を変更するメンバ関数 .setVisibility() も作成します。

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

class Item
{
public: // クラスの外からアクセスできる

	// クラスの初期化のためのメンバ関数(コンストラクタ)
	Item(const Emoji& emoji, const Circle& circle, bool visible)
		: m_texture{ emoji }
		, m_circle{ circle }
		, m_visible{ visible }
	{
		// 特に何もしない
	}

	// Visibility の変更
	void setVisibility(bool visible)
	{
		m_visible = visible;
	}

	// カーソルスタイルの更新のためのメンバ関数
	void updateCursorStyle() const
	{
		if (!m_visible)
		{
			return;
		}

		if (m_circle.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hand);
		}
	}

	// クリックされたかを返すメンバ関数
	bool clicked() const
	{
		if (!m_visible)
		{
			return false;
		}

		return m_circle.leftClicked();
	}

	// 描画のためのメンバ関数
	void draw() const
	{
		if (!m_visible)
		{
			return;
		}

		m_circle.draw(ColorF{ 1.0, 0.5, 0.0, 0.2 });
		m_texture.drawAt(m_circle.center);
	}

private: // クラスの外からはアクセスさせない

	Texture m_texture;

	Circle m_circle;

	bool m_visible;
};

void Main()
{
	Item ticket{ Emoji{ U"🎫" }, Circle{ 200, 300, 80 }, true };
	Item flashlight{ Emoji{ U"🔦" }, Circle{ 600, 300, 80 }, true };
	Item door{ Emoji{ U"🚪" }, Circle{ 400, 300, 80 }, false }; // ドアは最初は非表示

	// 明かりが付いているか
	bool lightOn = false;

	while (System::Update())
	{
		if (lightOn)
		{
			Scene::SetBackground(ColorF{ 0.9, 0.85, 0.8 });
		}
		else
		{
			Scene::SetBackground(ColorF{ 0.3, 0.3, 0.3 });
		}

		ticket.updateCursorStyle();
		flashlight.updateCursorStyle();
		door.updateCursorStyle();

		if (ticket.clicked())
		{
			ClearPrint();
			
			if (lightOn)
			{
				Print << U"映画のチケットだ。";
			}
			else
			{
				Print << U"何かの紙切れだ。暗くてよく読めない。";
			}
		}

		if (flashlight.clicked())
		{
			ClearPrint();
			Print << U"懐中電灯の明かりを付けた。";
			lightOn = true;
			door.setVisibility(true); // ドアを visible にする
		}

		if (door.clicked())
		{
			ClearPrint();
			Print << U"ドアから外に出た。";
		}

		ticket.draw();
		flashlight.draw();
		door.draw();
	}
}

12. テキストをしっかりと表示する


画面の好きな場所にテキストを表示できるようにします。

テキストを好きな場所に表示するには、Font を用意します。表示するテキストは String 型の変数に用意します。Font 型の変数に ()String 型の変数を渡し、.draw() することで、指定した場所に指定した色でテキストを表示できます。

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

class Item
{
public: // クラスの外からアクセスできる

	// クラスの初期化のためのメンバ関数(コンストラクタ)
	Item(const Emoji& emoji, const Circle& circle, bool visible)
		: m_texture{ emoji }
		, m_circle{ circle }
		, m_visible{ visible }
	{
		// 特に何もしない
	}

	// Visibility の変更
	void setVisibility(bool visible)
	{
		m_visible = visible;
	}

	// カーソルスタイルの更新のためのメンバ関数
	void updateCursorStyle() const
	{
		if (!m_visible)
		{
			return;
		}

		if (m_circle.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hand);
		}
	}

	// クリックされたかを返すメンバ関数
	bool clicked() const
	{
		if (!m_visible)
		{
			return false;
		}

		return m_circle.leftClicked();
	}

	// 描画のためのメンバ関数
	void draw() const
	{
		if (!m_visible)
		{
			return;
		}

		m_circle.draw(ColorF{ 1.0, 0.5, 0.0, 0.2 });
		m_texture.drawAt(m_circle.center);
	}

private: // クラスの外からはアクセスさせない

	Texture m_texture;

	Circle m_circle;

	bool m_visible;
};

void Main()
{
	Scene::SetBackground(ColorF{ 0.9, 0.85, 0.8 });

	Item apple{ Emoji{ U"🍎" }, Circle{ 200, 300, 80 }, true };
	Item cake{ Emoji{ U"🍰" }, Circle{ 600, 300, 80 }, true };

	// 大きさ 25 のフォント
	Font font{ 25 };

	// テキスト用の変数
	String text;

	while (System::Update())
	{
		apple.updateCursorStyle();
		cake.updateCursorStyle();

		if (apple.clicked())
		{
			text = U"机の上にリンゴが置いてある。";
		}

		if (cake.clicked())
		{
			text = U"おいしそうなケーキだ。";
		}

		apple.draw();
		cake.draw();

		// テキストを表示
		font(text).draw(40, 460, ColorF{ 0.1, 0.1, 0.1 });
	}
}

13. テキストの表示をクラスにする

テキストの表示に使う FontString を管理するクラス MessageBox を作ります。

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

class Item
{
public: // クラスの外からアクセスできる

	// クラスの初期化のためのメンバ関数(コンストラクタ)
	Item(const Emoji& emoji, const Circle& circle, bool visible)
		: m_texture{ emoji }
		, m_circle{ circle }
		, m_visible{ visible }
	{
		// 特に何もしない
	}

	// Visibility の変更
	void setVisibility(bool visible)
	{
		m_visible = visible;
	}

	// カーソルスタイルの更新のためのメンバ関数
	void updateCursorStyle() const
	{
		if (!m_visible)
		{
			return;
		}

		if (m_circle.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hand);
		}
	}

	// クリックされたかを返すメンバ関数
	bool clicked() const
	{
		if (!m_visible)
		{
			return false;
		}

		return m_circle.leftClicked();
	}

	// 描画のためのメンバ関数
	void draw() const
	{
		if (!m_visible)
		{
			return;
		}

		m_circle.draw(ColorF{ 1.0, 0.5, 0.0, 0.2 });
		m_texture.drawAt(m_circle.center);
	}

private: // クラスの外からはアクセスさせない

	Texture m_texture;

	Circle m_circle;

	bool m_visible;
};

class MessageBox
{
public:

	void setText(const String& text)
	{
		m_text = text;
	}

	void draw() const
	{
		m_font(m_text).draw(40, 460, ColorF{ 0.1, 0.1, 0.1 });
	}

private:

	Font m_font = Font{ 25 };

	String m_text;
};

void Main()
{
	Scene::SetBackground(ColorF{ 0.9, 0.85, 0.8 });

	Item apple{ Emoji{ U"🍎" }, Circle{ 200, 300, 80 }, true };
	Item cake{ Emoji{ U"🍰" }, Circle{ 600, 300, 80 }, true };

	MessageBox messageBox;

	while (System::Update())
	{
		apple.updateCursorStyle();
		cake.updateCursorStyle();

		if (apple.clicked())
		{
			messageBox.setText(U"机の上にリンゴが置いてある。");
		}

		if (cake.clicked())
		{
			messageBox.setText(U"おいしそうなケーキだ。");
		}

		apple.draw();
		cake.draw();
		messageBox.draw();
	}
}

14. メッセージボックスの表示をこだわる


テキストの背景に図形を描きます。

位置 (x, y) から幅 w, 高さ h の長方形を描くには Rect{ x, y, w, h }.draw(); します。角の丸み r を指定して RoundRect{ x, y, w, h, r }.draw() することもできます。.draw() で色を指定しなかった場合は白 (ColorF{ 1.0, 1.0, 1.0 }) になります。

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

class Item
{
public: // クラスの外からアクセスできる

	// クラスの初期化のためのメンバ関数(コンストラクタ)
	Item(const Emoji& emoji, const Circle& circle, bool visible)
		: m_texture{ emoji }
		, m_circle{ circle }
		, m_visible{ visible }
	{
		// 特に何もしない
	}

	// Visibility の変更
	void setVisibility(bool visible)
	{
		m_visible = visible;
	}

	// カーソルスタイルの更新のためのメンバ関数
	void updateCursorStyle() const
	{
		if (!m_visible)
		{
			return;
		}

		if (m_circle.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hand);
		}
	}

	// クリックされたかを返すメンバ関数
	bool clicked() const
	{
		if (!m_visible)
		{
			return false;
		}

		return m_circle.leftClicked();
	}

	// 描画のためのメンバ関数
	void draw() const
	{
		if (!m_visible)
		{
			return;
		}

		m_circle.draw(ColorF{ 1.0, 0.5, 0.0, 0.2 });
		m_texture.drawAt(m_circle.center);
	}

private: // クラスの外からはアクセスさせない

	Texture m_texture;

	Circle m_circle;

	bool m_visible;
};

class MessageBox
{
public:

	void setText(const String& text)
	{
		m_text = text;
	}

	void draw() const
	{
		Rect(20, 440, 760, 140).draw(ColorF{ 0.5, 0.5, 0.5 });
		RoundRect(30, 450, 740, 120, 10).draw();

		m_font(m_text).draw(40, 460, ColorF{ 0.1, 0.1, 0.1 });
	}

private:

	Font m_font = Font{ 25 };

	String m_text;
};

void Main()
{
	Scene::SetBackground(ColorF{ 0.9, 0.85, 0.8 });

	Item apple{ Emoji{ U"🍎" }, Circle{ 200, 300, 80 }, true };
	Item cake{ Emoji{ U"🍰" }, Circle{ 600, 300, 80 }, true };

	MessageBox messageBox;

	while (System::Update())
	{
		apple.updateCursorStyle();
		cake.updateCursorStyle();

		if (apple.clicked())
		{
			messageBox.setText(U"机の上にリンゴが置いてある。");
		}

		if (cake.clicked())
		{
			messageBox.setText(U"おいしそうなケーキだ。");
		}

		apple.draw();
		cake.draw();
		messageBox.draw();
	}
}

15. 文字を一文字ずつ表示する

メッセージボックスのテキストを 1 文字ずつ表示するようにします。

String のメンバ関数 .substr(offset, count) は、文章のうち offset 番目 (0 スタート) から count 文字だけを取り出します。count は実際の文字数をオーバーしても大丈夫です。

// 3 文字だけ表示
m_font(m_text.substr(0, 3)).draw(40, 460, ColorF{ 0.1, 0.1, 0.1 });

この機能と、経過時間を計測する Stopwatch を組み合わせることで、経過時間に応じて表示する文字数を増やすことができます。Stopwatch.restart() で 0 から開始し、.ms() で経過時間をミリ秒 (1 秒は 1000 ミリ秒) で取得できます。

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

class Item
{
public: // クラスの外からアクセスできる

	// クラスの初期化のためのメンバ関数(コンストラクタ)
	Item(const Emoji& emoji, const Circle& circle, bool visible)
		: m_texture{ emoji }
		, m_circle{ circle }
		, m_visible{ visible }
	{
		// 特に何もしない
	}

	// Visibility の変更
	void setVisibility(bool visible)
	{
		m_visible = visible;
	}

	// カーソルスタイルの更新のためのメンバ関数
	void updateCursorStyle() const
	{
		if (!m_visible)
		{
			return;
		}

		if (m_circle.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hand);
		}
	}

	// クリックされたかを返すメンバ関数
	bool clicked() const
	{
		if (!m_visible)
		{
			return false;
		}

		return m_circle.leftClicked();
	}

	// 描画のためのメンバ関数
	void draw() const
	{
		if (!m_visible)
		{
			return;
		}

		m_circle.draw(ColorF{ 1.0, 0.5, 0.0, 0.2 });
		m_texture.drawAt(m_circle.center);
	}

private: // クラスの外からはアクセスさせない

	Texture m_texture;

	Circle m_circle;

	bool m_visible;
};

class MessageBox
{
public:

	void setText(const String& text)
	{
		m_text = text;
		m_stopwatch.restart(); // ストップウォッチを 0 からスタート
	}

	void draw() const
	{
		Rect(20, 440, 760, 140).draw(ColorF{ 0.5, 0.5, 0.5 });
		RoundRect(30, 450, 740, 120, 10).draw();

		const int32 count = (m_stopwatch.ms() / 100); // 100 ミリ秒につき 1 文字 
		m_font(m_text.substr(0, count)).draw(40, 460, ColorF{ 0.1, 0.1, 0.1 });
	}

private:

	Font m_font = Font{ 25 };

	String m_text;

	Stopwatch m_stopwatch;
};

void Main()
{
	Scene::SetBackground(ColorF{ 0.9, 0.85, 0.8 });

	Item apple{ Emoji{ U"🍎" }, Circle{ 200, 300, 80 }, true };
	Item cake{ Emoji{ U"🍰" }, Circle{ 600, 300, 80 }, true };

	MessageBox messageBox;

	while (System::Update())
	{
		apple.updateCursorStyle();
		cake.updateCursorStyle();

		if (apple.clicked())
		{
			messageBox.setText(U"机の上にリンゴが置いてある。");
		}

		if (cake.clicked())
		{
			messageBox.setText(U"おいしそうなケーキだ。");
		}

		apple.draw();
		cake.draw();
		messageBox.draw();
	}
}

▶ Day 3 に続く(準備中)

Discussion

ログインするとコメントできます