Siv3D Advent Calendar 2016 2 日目の記事です。
ゲームはグラフィックとかサウンドとかシナリオが命とはよく言われているけど、よりよいゲームを作るには基盤部分のシステムが大事なのです。
きょうは、GUI機能を用いて、パラメータ管理システムの基本的な作り方を紹介します。
Siv3DのGUIとは
Siv3Dで扱うGUIは、図のようなテキストボックスやボタン、スライダーなどといった物体のこと。Webブラウザに表示されるテキストボックスやボタンなどに近い見た目をしています。別にゲーム制作においては必要不可欠なものではありません。見た目にこだわらないからパラメーター等をいじる機能がほしいというときに、このGUIが役に立ちます。
基本的な使い方
# include <Siv3D.hpp>
void Main()
{
GUI gui(GUIStyle::Default);
gui.setTitle(L"GUIのタイトル");
gui.add(L"text1", GUIText::Create(L"もじひょうじ"));
gui.addln(L"button1", GUIButton::Create(L"ぼたん"));
while (System::Update())
{
}
}
こんなかんじのコードを書けばGUIは作れます。GUIクラスのオブジェクトを1つ作り、そこに機能をaddしていくだけでそれっぽいものが作れます。また、GUIは非表示にする設定をしない限り、drawしなくても画面前部に描画されます。
上記のコードだと、ボタンを押しても何の反応がありません。ボタンが押されたときの処理はプログラム中に追加する必要があります。また、Styleをいじることによってテキストの右揃え左揃えを変更したり、テキストやボタンなどの幅を設定したりできます。
GUIの基本機能は公式リファレンスを参照するといいでしょう。https://github.com/Siv3D/Reference-JP/wiki/GUI
もっといろいろ使いやすくする
GUIをもっと使いやすいものにしたい。そう思うことはある。
たとえば、0~1000までの数字を出すスライダーを作りたいとき、スライダー単体を左右に動かすだけでは数値がいくつなのか確認できない。
Textを使って数値を表示したけど、今度は細かく調整できないことに気づく。
その場合、数字を手入力できるようにTextFieldを合わせて使うのがいい。
TextFieldに数値を打ち込むと、スライダーも自動的にその数値に移動する。また、スライダーを動かすとTextFieldの表示が変化する。この両方を実現させるプログラムを組めばいいのです。
そのプログラムの一例はこちらになります。
# include <Siv3D.hpp>
void Main()
{
GUI gui(GUIStyle::Default);
gui.setTitle(L"スライダーと数値入力");
gui.add(L"tField1", GUITextField::Create(6));
gui.textField(L"tField1").setText(L"0");
gui.addln(L"slider1", GUISlider::Create(0.0, 1000.0, 0.0, 200));
while (System::Update())
{
// スライダーが変化したら、数値を変更する
if (gui.slider(L"slider1").hasChanged)
{
gui.textField(L"tField1").setText(Format(gui.slider(L"slider1").value));
}
// 数値が変化したら、スライダーを変更する
if (gui.textField(L"tField1").hasChanged)
{
gui.slider(L"slider1").setValue(Parse<double>(gui.textField(L"tField1").text));
}
}
}
カスタマイズ次第では、もっとすごい機能が作れるかもしれない。
複数のGUIを使ったページ切り替え機能とかを作るのもいいかもしれない。
しかし、そんなGUIでも不便なところがある。TextFieldやTextAreaは、末尾にしか文字を追加できない仕様が不便。一般的なテキストエディタのように、文字の途中を選択したらそこに挿入できないものだろうか? (実装希望)
セーブとGUIを使って編集する必要性
GUI上で設定したスライダーなどの数値は、通常のファイル保存と同様にCSVファイルに保存することができる。もちろん、セーブデータを読み込んでGUIの数値を設定することもできる。
保存したCSVのデータは、ゲーム本体と共通のデータ形式にすればゲーム本体でも使える。
# include <Siv3D.hpp>
void Main()
{
GUI gui(GUIStyle::Default);
gui.setTitle(L"スライダーと数値入力");
gui.add(L"tField1", GUITextField::Create(4));
gui.textField(L"tField1").setText(L"0");
gui.addln(L"slider1", GUISlider::Create(0.0, 1000.0, 0.0, 200));
gui.add(L"button1", GUIButton::Create(L"ロード"));
gui.addln(L"button2", GUIButton::Create(L"セーブ"));
while (System::Update())
{
// スライダーが変化したら、数値を変更する
if (gui.slider(L"slider1").hasChanged)
{
gui.textField(L"tField1").setText(Format(gui.slider(L"slider1").value));
}
// 数値が変化したら、スライダーを変更する
if (gui.textField(L"tField1").hasChanged)
{
gui.slider(L"slider1").setValue(Parse<double>(gui.textField(L"tField1").text));
}
// データのロード
if (gui.button(L"button1").pushed)
{
if (const auto open = Dialog::GetOpen({ ExtensionFilter::CSV }))
{
CSVReader csv(open.value());
gui.slider(L"slider1").setValue(csv.get<double>(0, 0));
gui.textField(L"tField1").setText(Format(gui.slider(L"slider1").value));
}
}
// データのセーブ
if (gui.button(L"button2").pushed)
{
if (const auto open = Dialog::GetSave({ ExtensionFilter::CSV }))
{
CSVWriter csv(open.value());
csv.writeRow(gui.slider(L"slider1").value);
}
}
}
}
ファイル名が固定であるならば、ファイルダイアログを開かずに直接ソースコードにファイル名を入力してもいい。
実際にはもっとたくさんの要素を読み書きすることになる。CSVの行と列をずらすことで複数要素のデータを読み書きできる。
ただ単にCSVに出力して保存するだけなら、「CSVファイル直打ちでいいじゃん」とならないか?
でもGUIでエディタを作る意味はあります。
GUIエディタを利用すれば、数値計算ができます。たとえば、HPの5倍、攻撃の2倍、防御の1倍の合計が1000を超えないようにルールを作ったとする。これを手計算で調べるのはいちいちめんどくさい。
GUIでプログラムを組めば、スライダーのつまみを動かし数値を変更しても、瞬時に合計値が出ます。
また、GUIで管理されている変数は、GUI外の画像描画領域に持っていくことができます。GUIで設定した数値のグラフとかを描くこともできます。
あとはGUIを使えばデータの管理が楽ということ。テキストラベルに説明文を入れておけば、CSVに無駄な説明文を入れることなく、どの要素が何の役割を持っているかわかりやすくなる。
GUIを使えるようになるとSiv3D使いのレベルが1つ上がるぞ!
さいごに
自分はこのGUIで作ったパラメータ管理システムを使って、対戦型パズルゲームを作っています。
まだ対戦中の画面は見せられないけど、そのパラメータ管理システムの画像を上げておきます。
キャラクターごとに「持ちブロック」が異なり、それぞれ違うパターンのブロックが出現します。その他そんな作品を来年末を目標に・・・ 作っています。
・キャラクター画像「テックちゃん」
© 工大祭実行委員会 / ヒダ https://koudaisai.jp/mascot
明日の記事は @hamukun8686 さんです。
よろしくお願いします。