Help us understand the problem. What is going on with this article?

Siv3DでサクッとGUIを作る

この記事はリンク情報システムの2018年アドベントカレンダーのリレー記事です。
engineer.hanzomon のグループメンバーによってリレーされます。
(リンク情報システムのFacebookはこちらから)

はじめに

勉強会の成果報告で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

GUI.png

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

インストールとHelloWorld

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

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

画像の出し方

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

やり方はこちらを見たほうが分かりやすいです。
本家リファレンス

アセットハンドルの登録

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

アセットの登録
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"任意の名前")で登録した画像やフォントを使用できます。
以下の処理は、適当に取得して、手作業で調整した位置に描画してるだけです。

アセットからの呼び出し
// プレイヤー画像と得点表示の更新
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;
    }
}

メインループから呼ぶ

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

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

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

    //省略

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

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

Siv3Dで作って良かったこと

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

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

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

(おまけ)Siv3DからOpenSiv3Dに移行した時のエラー

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

Scaleメソッドが無い

修正前
TextureAsset(U"Town_R").scale(0.5, 0.5).draw(250 + i * 10, 300 + i * 10);
正解
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を使用するとエラー

修正前
    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);
正解
    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': 非標準の構文です。メンバーにポインタを作成するには '&' を使用してください

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

Texture.hpp
[[nodiscard]] int32 width() const;

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

GUIAssetが無い

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


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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away