DxLibとC++でゲームを作りたい

ゲームづくりのための覚え書き

UIの作り方ってどうすんの?

UIの枠を作りたい

DxLibでゲームを作る時にネックになるのがUIの作り方。

Unityみたいに分かりやすく簡単に作る方法なんて無く、自分で作る必要がある。

でもそれってどうやんの?画像一つ一つサイズ決めて作って貼っ付ける?

コストやべー・・・・・・

 

てなことでUIの作り方について書いていきます。

 

  • UIの枠を作りたいんだけど

    まずはここから。文字とか画面上に表示する時に、背景が欲しくなる。ウィンドウ的なやつ。でもその時のサイズってまちまちで、縦に並ぶメニューとか画面下部のステータス描画とかゲームクリア時に表示するwindowとか・・・
    さて、DxLibには画像を描画するための関数が多数あります。その中から便利なやつを使ってサイズを変えられるMenuクラスを作ってみましょう。

    必要な要件を並べます。
    1.横幅と縦幅を指定して枠画像を描画出来ること
    2.使用する元画像が一つで済むこと
    3.画像はサイズが同じなら何でも使用できること

    ということでこれらを実現するために考えていきます。というかまずは作ったクラスを書きますね。

    ------Menu.h------
    #pragma once
    #include<string>
    #include<vector>

    class GameManager;

    class Menu{
    public:
     Menu() {};
        Menu(int MenuTopX, int MenuTopY, int Width,
            int Height, std::string GhPath);
        ~Menu() {};

     //背景描画
        void MenuDraw();

        //左上座標取得
        inline const float* GetTopPos() {
            return menuTopPos;
        }

        //幅取得
        inline const int& GetWidth() {
            return width;
        }

        //高さ取得
        inline const int& GetHeight() {
            return height;
        }

    protected:
        //メニュー左上座標(x,y)
        float menuTopPos[2] = {};
     

     //メニュー横幅
        int width = 0;
        //メニュー縦幅
        int height = 0;

        //メニュー背景画像パス
        std::string menuBackGroundGhPass = "";
        //メニュー背景画像ハンドル
        std::vector<int >menuBackGroundGh = {};

        GameManager* gManager = nullptr;

          }
   ------Menu.cpp------
#include "Menu.h"
#include"Manager/GameManager.h"
   

//コンストラク

Menu::Menu(int MenuTopX, int MenuTopY, int Width, int Height, std::string GhPath)
{
//インスタンス取得
    gManager = GameManager::Instance();

//左上座標の決定
    menuTopPos[0] = MenuTopX;
 menuTopPos[1] = MenuTopY;
//横と縦の幅決定

    width = Width;
    height = Height;
//相対パスの決定
    menuBackGroundGhPass = GhPath;
//画像の分割ロード(3x3の9枚、元画像は16x16のサイズ固定)
 gManager->LoadDivGraphEx(GhPath, 9, 3, 3, 16, 16, menuBackGroundGh);
}
//枠の描画(分割ロードした画像の中心部を引き伸ばして形を決める)

void Menu::MenuDraw()
{
    //上段
    DrawGraph(menuTopPos[0], menuTopPos[1], menuBackGroundGh[0], true);
    DrawExtendGraph(menuTopPos[0] + 16, menuTopPos[1],
         menuTopPos[0] + width - 16, menuTopPos[1] + 16,
         menuBackGroundGh[1], true);
    DrawGraph(menuTopPos[0] + width - 16, menuTopPos[1], menuBackGroundGh[2], true);

    //中段
    DrawExtendGraph(menuTopPos[0], menuTopPos[1] + 16,
         menuTopPos[0] + 16, menuTopPos[1] + height - 16,          menuBackGroundGh[3], true);
    DrawExtendGraph(menuTopPos[0] + 16, menuTopPos[1] + 16,
          menuTopPos[0] + width - 16, menuTopPos[1] + height - 16,          menuBackGroundGh[4], true);
    DrawExtendGraph(menuTopPos[0] + width - 16, menuTopPos[1] + 16,
         menuTopPos[0] + width, menuTopPos[1] + height - 16,          menuBackGroundGh[5], true);

    //下段
    DrawGraph(menuTopPos[0], menuTopPos[1] + height - 16, menuBackGroundGh[6], true);
    DrawExtendGraph(menuTopPos[0] + 16, menuTopPos[1] + height - 16,
                                  menuTopPos[0] + width - 16, menuTopPos[1] + height, 
                                  menuBackGroundGh[7], true);
    DrawGraph(menuTopPos[0] + width - 16, menuTopPos[1] + height - 16, 
                       menuBackGroundGh[8], true);
}


一つずつ説明します。
まずはコンストラクタについて。引数は以下の通り。

  int MenuTopX, int MenuTopY, int Width, int Height, std::string GhPath
まぁ名前から大体わかると思いますが、このクラスのインスタンスを生成する時にUI画像の左上の座標、横幅、縦幅、使う画像のパス(slnファイルからのフォルダの相対位置)を決めます。
一番大事なのは
 //画像の分割ロード(3x3の9枚、元画像は48x48のサイズ固定)
 gManager->LoadDivGraphEx(GhPath, 9, 3, 3, 16, 16, menuBackGroundGh);

この部分です。
DxLibのLoadDivGraph関数は指定した枚数、指定した大きさで画像を分割し、int型の配列に格納する関数です。ですが、ここでは配列ではなくvectorを使いたいため、そのままの関数では使えません。そこで、GameManagerにLoadDivGraphEx関数を作っています。大したことはしておらず、ただ普通にLoadDivGraphをした上でvectorに格納しているだけなのでそこは気にしないでください。

何より大事なのは、48x48とかなり小さい正方形のUI画像をさらに3x3の9分割している点です。

LoadDivGraph_1



この分割後の中心の十字部分を引き伸ばすことで好きな大きさのUIを作成できます。
ただ、その作り方の都合上、DrawRotaGraphが使用できない点と、48x48より小さいUIは作成できない弱点があります。

DrawGraph関数は画像の描画基準点は左上なので、そこに注意する必要があります。
(DrawRotaGraphは基準点が画像中心です)

次に難しく見えるDrawMenu関数についてです。
引数が大量すぎてくっそ難しそうに見えますが、怖がらないでください。

 DrawGraph(menuTopPos[0], menuTopPos[1], menuBackGroundGh[0], true);
引数は4つ。画像の基準点のx,y,描画する画像ハンドル,透過判定です。
コンストラクタでx,yは決定しているので、配列からそれぞれ使用し、画像ハンドルは9分割した配列の左上->0番を指定します。最後は基本trueで大丈夫です。

DrawExtendGraph(menuTopPos[0] + 16, menuTopPos[1],
         menuTopPos[0] + width - 16, menuTopPos[1] + 16,
         menuBackGroundGh[1], true);
この関数が鬼門です。
DrawExtendGraphは基準点を左上とした時に、画像の右下を第三,四引数の位置にすることで画像を引き伸ばして描画する関数です。なんのこっちゃって感じですよね。
とりあえずそういうもんだと思ってください。

ここで大事なのは+16,-16の意味と、widthとはなんぞやという点です。
なぜ画像の左上の座標がmenuTopPos[0] + 16なのか。ぜひノートかなんかに適当な長方形を書いて、9分割して書きながら読んで下さい。

1枚目は9枚のうちの左上を描画しました。今は2枚目、一番上の段の真ん中です。
この画像を引き伸ばすわけですが、開始点=左上は分割画像の大きさである16分だけ右にずらさなければいけません。じゃないと被ってしまいますよね?
なので+16をしているわけです。
そして一番上の段なのでy座標は何も足しません。

問題は menuTopPos[0] + width - 16 こいつです。
第三引数は画像の右下のx座標なわけですが、UI全体の横幅を足したあとで分割画像一枚分の大きさを引いています。
これもノートと見比べてみてください。
右下の座標はUI全体の横幅を3分割した中の真ん中右端です。
つまり、menuTopPos[0] + widthのままだと一番右端を表す座標になってしまうため、
分割画像一枚分の大きさである16を引く必要があるんですね。

ここまで読んで理解すれば中段と下段もどういう理由で引数が入っているか分かってくると思います。重要なのは、これらの関数の基準座標は全て左上基準であること、分割画像の大きさは16であること、引き伸ばすExtendGraphを使うのは9分割した中での十字部分であることです。


関数の説明は以上ですが、.hファイルにも見慣れないものがあると思います。

inline~とか inline const float*~とか...

inlineはググれば出ますが、今は気にしないでいいです。インライン展開とかいうワードで勉強すればなんとなくわかるはずです。

返り値のconstは取得した先で値を変更しないよという理由でつけています。どちらかというと、プログラムを読んだ共同開発者や採用担当者に向けたメッセージです。

float*についてですが、配列を返り値や引数に使う際にはポインタで表します。

まぁ個人的にはvectorが使いやすいので、inline const std::vector& GetPos()~としたい所ですが今回は普通の配列を使いました。

 inline const int&の&は参照渡しです。詳細は難しいので今は気にしないでください。なくても良いです。

最後に使うときの書き方です。

まずポインタ変数を宣言します。

#include"Menu.h"

Menu* ui=nullptr;

インスタンスを生成します。

ui=new Menu(300,300,200,100,"graphics/MenuGraphic.png");

ここで入れた数字やgraphics/MenuGraphic.pngがコンストラクタの引数です。
(x,y)=(300,300)を左上として、横200,縦100の大きさでgraphicsフォルダにあるMenuGraphic.pngという画像を引き伸ばしてUIを作るぜ!となります。

そして毎フレーム実行するDraw関数の中でMenuDraw関数を呼びます。

void Draw(){

ui->MenuDraw();

}
これでUIが指定した座標に指定した大きさで描画されます。
ただ、こいつ画像を引き伸ばして描画する上左上が基準点だし一々実行しないと確認できないしで使いづらいですよね?

あーどっかにUIエディターっぽいやつ転がってないかなー

・・・

 

github.com