Siv3DでサクッとGUIを作る


この記事は[リンク情報システム](https://www.lis.co.jp/)の2018年アドベントカレンダーのリレー記事です。
engineer.hanzomon のグループメンバーによってリレーされます。
（リンク情報システムのFacebookは[こちら](https://www.facebook.com/lis.co.jp)から）

# はじめに

勉強会の成果報告でGUIを作った時に使用したライブラリ「Siv3D」を紹介します。
楽しくプログラミングが出来たので、初めてGUIを作る人にもオススメです。

# 背景
2017年にC++の勉強の一環としてボードゲームのプログラミングに取り組んでいました。
社内で発表することになったのですが、GUIがあった方が見栄えが良いので作ることに。

何か適当にライブラリ探すか～とC++ GUIとかで検索してたら良さげだったので使ってみました。
そのため、この記事のコードは主に2017年12月時点での情報で構成されています。一応注意です。


#Siv3Dとは
アプリケーション開発のための、C++のライブラリです。
できることは本家サイトが分かりやすいので見たら帰ってきてください。
https://play-siv3d.hateblo.jp/

AdventCalendarも開催中です。
https://qiita.com/advent-calendar/2018/siv3d


# 作ったもの

友人が作成した「ネコの集会」というカードを使ったボードゲームのプログラムを作成しました。
ルールが気になる方は公式サイトをチェックして買ってください。（ダイレクトマーケティング）
https://gurua-games-nekonoshuukai.jimdo.com/

![image.png](https://qiita-image-store.s3.amazonaws.com/0/289156/d59a32fe-cc98-29b2-2971-383deaed948b.png)

![GUI.png](https://qiita-image-store.s3.amazonaws.com/0/289156/8dadc66f-fb0e-6e3d-8eb0-e995f6f4d340.png)

**とってもシンプルでヤバイですね☆**
画像置いただけのヤバイ画面ですが、インストール含めて数時間で出来ました。

#インストールとHelloWorld
VisualStudioがあれば、インストーラに従ってインストールするだけで完了です。
https://github.com/Siv3D/Reference-JP/wiki/ダウンロードとインストール

プロジェクトを新規作成したら空の画面が生成されます。簡単すぎてヤバイですね☆

# 画像の出し方

~~ただ画像並べただけなので、あんまり書くことが無いのですが、~~
画面右側のネコアニキ達を表示している部分を使って、画像の出し方を紹介します。

やり方はこちらを見たほうが分かりやすいです。
[本家リファレンス](https://github.com/Reputeless/Siv3D-Reference/blob/master/Programming%20Guide/Asset.md)

## アセットハンドルの登録

xxxAsset::Register()で対応するアセットのハンドルを登録できます。
最初の引数はそのハンドルの名前です。
その後、Textureならファイルパス、Fontなら文字の大きさや装飾を指定します。

```C++:アセットの登録
void RegisterAsset()
{
	//省略

	TextureAsset::Register(L"P1", L"Data/ネコアニキ.png");
	TextureAsset::Register(L"P2", L"Data/ジニャン.png");
	TextureAsset::Register(L"P3", L"Data/サンニャン.png");
	TextureAsset::Register(L"P4", L"Data/ヨンニャン.png");

	FontAsset::Register(L"SCORE", 35, Typeface::Black, FontStyle::Outline);
	
	//省略

}
```

## 登録したアセットハンドルを使う

xxxAsset(U"任意の名前")で登録した画像やフォントを使用できます。
以下の処理は、適当に取得して、手作業で調整した位置に描画してるだけです。


```C++:アセットからの呼び出し
// プレイヤー画像と得点表示の更新
void UpdatePlayer()
{
	Player * const pP1 = game.GetPlayerInfo(0);
	Player * const pP2 = game.GetPlayerInfo(1);
	Player * const pP3 = game.GetPlayerInfo(2);
	Player * const pP4 = game.GetPlayerInfo(3);

	TextureAsset(L"P1").scale(0.4, 0.4).draw(500, 50);
	TextureAsset(L"P2").scale(0.4, 0.4).draw(500, 150);
	TextureAsset(L"P3").scale(0.4, 0.4).draw(500, 250);
	TextureAsset(L"P4").scale(0.4, 0.4).draw(500, 350);

	FontAsset(L"SCORE")(pP1->NowTotalPoint()).drawCenter(
		500 + TextureAsset(L"P1").width/2  *0.4 + 10, 
		50  + TextureAsset(L"P1").height/2 *0.4, 
		Palette::Lightgreen);

	FontAsset(L"SCORE")(pP2->NowTotalPoint()).drawCenter(
		500 + TextureAsset(L"P2").width / 2 * 0.4 + 10,
		150 + TextureAsset(L"P2").height / 2 * 0.4,
		Palette::Lightgreen);

	FontAsset(L"SCORE")(pP3->NowTotalPoint()).drawCenter(
		500 + TextureAsset(L"P3").width / 2 * 0.4 + 10,
		250 + TextureAsset(L"P3").height / 2 * 0.4,
		Palette::Lightgreen);

	FontAsset(L"SCORE")(pP4->NowTotalPoint()).drawCenter(
		500 + TextureAsset(L"P4").width / 2 * 0.4 + 10,
		350 + TextureAsset(L"P4").height / 2 * 0.4,
		Palette::Lightgreen);

	//ターンプレイヤーに矢印を出す
	switch (turn_player)
	{
		case 0:
			Line(
				450, 50 + TextureAsset(L"P1").height / 2 * 0.4,
				500, 50 + TextureAsset(L"P1").height / 2 * 0.4
			).drawArrow(5, { 100, 60 }, Palette::Lightgreen);
			break;

		case 1:
			Line(
				450, 150 + TextureAsset(L"P2").height / 2 * 0.4,
				500, 150 + TextureAsset(L"P2").height / 2 * 0.4
			).drawArrow(5, { 100, 60 }, Palette::Lightgreen);
			break;

		case 2:
			Line(
				450, 250 + TextureAsset(L"P3").height / 2 * 0.4,
				500, 250 + TextureAsset(L"P3").height / 2 * 0.4
			).drawArrow(5, { 100, 60 }, Palette::Lightgreen);
			break;

		case 3:
			Line(
				450, 350 + TextureAsset(L"P4").height / 2 * 0.4,
				500, 350 + TextureAsset(L"P4").height / 2 * 0.4
			).drawArrow(5, { 100, 60 }, Palette::Lightgreen);
			break;

		default:
			System::Exit();
			break;
	}
}

```

## メインループから呼ぶ

上記の関数をメインループ内でコールしてゲーム状況を表示しています。

```C:メインループでの処理
void Main()
{
	//省略

	/* アセット登録 */
	RegisterAsset();

	//省略

	while (System::Update())
	{
		/* 画像アップデート */
		UpdatePlace();
		UpdatePlayer();

		/* ゲームを進める */
		//省略
	}
}
```

# Siv3Dで作って良かったこと
想像以上の簡単さで画面が作れてとっても助かりました。
作業をしていて感じた、良かった点は以下の通りです。

- インストーラーがあるので導入が簡単
- リファレンスが分かりやすい
- 短いコードで書けて、とりあえずの画面ならサクッと作れる

普段本格的なアプリケーションを開発してないけど画面を作りたい。
そんな私のような方に是非試していただきたいです。


# （おまけ）Siv3DからOpenSiv3Dに移行した時のエラー

~~(文章の水増しのために)~~折角なので最新バージョンであるOpenSiv3Dに移行してみました。
以下エラーが出た時の備忘録です。

## Scaleメソッドが無い

```C++:修正前
TextureAsset(U"Town_R").scale(0.5, 0.5).draw(250 + i * 10, 300 + i * 10);
```

```C++:正解
TextureAsset(U"Town_R").scaled(0.5, 0.5).draw(250 + i * 10, 300 + i * 10);
```
scaledに変わってました。~~同様に、drawCenter()も無かったのでdraw()に置き換えました。~~

コメントを頂いたので、drawCenter()の置き換えを修正（2018/12/17）
> drawCenter() は OpenSiv3D では drawAt() になりました。



## widthを使用するとエラー
```C++:修正前
	FontAsset(U"SCORE")(pP1->NowTotalPoint()).draw(
		500 + TextureAsset(U"P1").width / 2  *0.4 + 10, 
		50  + TextureAsset(U"P1").height / 2 *0.4, 
		Palette::Lightgreen);
```
```C++:正解
	FontAsset(U"SCORE")(pP1->NowTotalPoint()).draw(
		500 + TextureAsset(U"P1").width() / 2  *0.4 + 10, 
		50  + TextureAsset(U"P1").height() / 2 *0.4, 
		Palette::Lightgreen);
```

()をつけたら通りました。
同様のコンパイルエラーは全部これで何とかなりました。（heightとかascentとか）

修正前のコードはOpenSiv3Dでコンパイルすると以下のエラーが出ます。

```
error C3867: 's3d::Texture::width': 非標準の構文です。メンバーにポインタを作成するには '&' を使用してください
```

エラーコードを見ても意味がわからなかったのですが、定義に飛んだら一瞬でした。

```C++:Texture.hpp
[[nodiscard]] int32 width() const;
```

関数(？)になってた。プロパティの使い方って色々あってムズカシイです。

## GUIAssetが無い

無いみたいです。SimpleGUIを使えばいいと思います。

***
明日の投稿は @n-tanimoto さんです。お楽しみに。