Siv3D | 脱出ゲームを作る Day 2
このコースでは、初歩的な C++ の文法と OpenSiv3D の機能を使って、シンプルな脱出ゲームを作ります。
8. クラスを作る
アイテムに関連する情報をクラスにまとめて管理しましょう。
Item
クラスを作り、private
なメンバ変数に Texture m_texture
と Circle 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. テキストの表示をクラスにする
テキストの表示に使う Font
と String
を管理するクラス 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