カテゴリー: Unity

うにのトゲは刺さると痛い(´・ω・`)-11

本格的にリファクタリングを試みる(`・ω・´)

凄くベタな書き方をしてきたのでリファクタリングをすることにした。
という風に書くと意識高いというか余裕があるように見えるが、実際は今のままだとカードセットの変更が非常に面倒くさいことになると気づいたからだった(ノ∀`)

今内部で処理しているトランプのカードセットと今後追加したいカードセットとでは構造が異なるので、まずはカード絡みの処理をインターフェイスや抽象クラスにしておくかと頑張った(`・ω・´)

初めにいびつな形でカード用のプレハブに貼り付けていたCardManagerを分離し、カードのparentになるCanvasに共通の処理のみを持つCardManagerを貼り付けて、各カードにはACardInfoを継承したクラスを貼り付け、そのクラスにACardDataというカード情報の実体を持たせた。各カードへカード情報を設定する為のACardSetを継承するクラスも作ったら一応、目的通りの挙動になった。まだ完成はしていないけれども(ノ∀`) 大枠の部分は出来た。

参考:
インターフェイスのプロパティ (C# プログラミング ガイド)

C#のインターフェイスってフィールドを持てないんだね(´・ω・`)
Javaは持てた気がするんだけども。仕方がないので抽象クラスにしたけども。
Why can’t C# interfaces contain fields?

eclipseに比べるとやっぱりなんだかVisual Studioは使いにくいねぇ(´・ω・`)
Visual C# のコード スニペット

試してはいないけれども、自分でコードスニペットを作ればいいのだろうか。
チュートリアル: コード スニペットを作成する

インターフェイスじゃなくて抽象クラスで受けてる(´・ω・`)
GetComponentを使うときはインターフェースを使おう


スクリプトをファイル名指定で動的に追加する

こんな感じらしい。つーかこれってスクリプトに限らず、あらゆるコンポーネントの動的追加方法か。取り敢えずstart()とかは動かない模様(´・ω・`)

targetGameObject.AddComponent(Type.GetType(scriptFileName));

参考:
Unityスクリプト実装メモ


EventTriggerの動的追加

Unity 動的にEventTriggerへ引数を持った関数を設定する方法の回答の通りにしてみたけど、何か追加されていないように思える(´・ω・`)

色々悩んで、ぐぐり直した結果、

注意点として、この方法で追加したeventはインスペクタに表示されません。
自分用Unityメモ:EventTriggerにスクリプトからEventを追加する

ということだった…_| ̄|○ ドウイウコトダネ…


Textの追加
本当は先に神経衰弱の追加要素を先に作ろうと思っていたが、国旗のデータセットを作ってしまったので、後で追加しようと思っていた文字表示を先にやっつけた。まだ細かいところを直していないのでアレだが、何はともあれ簡単にGameObject上に文字を表示出来るのが判って良かった(・∀・)

uGUIのパーツを一発生成
【Unit
y】ScriptからuGUIのTextにアクセスして内容を変更する


あとはPlayerPrefsリセットボタンを追加したり、タイトル画面にカード・タイプ選択用のドロップダウンを追加した。

参考にしたコードに

            //dropdown.RefreshShownValue();//更新を確認(画像を設定したとき)

と有ったのに、移植する際にコメントを削ってしまったが為にハマることに(ヽ’ω`)

別段画像を設定したわけではないのだけれども、RefreshShownValue()を入れないとIndexが0の場合に表示が真っ白になってしまった(´・ω・`)

内部的には問題はないようであったが表示がされないよう(´;ω;`)と悩んだ挙げ句、スクリプトリファレンスを眺めて、Dropdown.RefreshShownValueかヽ(`Д´)ノ と追加して解決してドヤ顔だったが、このエントリを書く際に参考にしたコードに既に注記されていたことに気づき、へこむ(´・ω・`)

参考:
Unityで、保存データ全消しのデバッグ用ボタンを配置する
【Unity】【C#】uGUI ドロップダウンの要素をコードで設定と取得、外観のカスタマイズなど

うにのトゲは刺さると痛い(´・ω・`)-10

データ作成で時間がかかる(´・ω・`)

一応、前回で音楽有りのカード枚数を変更したりすることが出来たので、新たな機能を追加しようと思ったが、適当に作っていたから色々とコードが酷い(ヽ’ω`)

というわけでちょこちょこリファクタリング…というよりも大手術を実施中。
その途中で新しいカードセットが必要となり、国旗のセットを作ろうと目論む。
なんやかんやで無料素材があったのだけれども、その他のデータもついでにちょっと載せようとしたが為に、中々データ作成が終了しない…_| ̄|○

国連加盟国が193だか194で、日本が認めているのが196くらい?
なんか国連の広報センターだかが出してる国連加盟一覧は2014年のもので内容が古かったり、外務省の正式名称とも違ったりしていてイヤンな感じである(´・ω・`)

謎のエラーにハマり中
そんなこんなでデータ作成に一日二日と掛けていてコードの方はほとんど触っておらず、Android端末での起動もしていなかった。久しぶりにちょっと起動して愕然とする…カードの表側が表示されずに真っ白……( ;・´ω・`)ゴクリッ
Unity Editor上では正しく表示される…

新しくFilePathを追加したのがいけないのかと思ったが、Unity Editor上では問題ないし…

色々と調べた結果、どうもAndroid端末上だとTextデータが正しく読み取れていないみたい。
その結果、imageをロード出来なくて真っ白白助になっている模様(´・ω・`)
カードをクリックしてマッチしてない場合は戻り、マッチしている場合は消えるので他のデータはきちんと読み込まれている。

なんだろう… “SQLiteUnityKit” の使い方を間違えているのか……ちょっと調べないといけないな…(ヽ’ω`)


と思った数時間後、原因は判明しないが一応の解決はした。

色々とぐぐっても同じような事象はこの一件だけ。しかも何のコメントもなし。
How to make Sqlite work for Android with Unity and SQLiteUnityKit

それ以外でヒットすることもなく。凄く悩んだ(´・ω・`)
最初は何らかの処理が足らないかと思ったが、そんなこともなく。
AndroidではTEXT型だけ読めないのだろうかと思ったが以前試したdbのプロジェクトでは何事もなく読み取れた。

コードの書き方が悪くて負荷が大きくなって読めないのかと思って色々試したが状況は変わらず。
StreamingAssets内のdbを一旦削除し、残しておいた元dbをもう一回フォルダに突っ込んだけれども状況は変わらず。

致し方なく以前上手く動いていたdbを今のプロジェクトに持って来て試したところ、TEXT型の内容が読み取れた( ・´ω・`)?
最終的にそのdbを改変して内容をコピーした結果、無事に前の通り動くようになった…_| ̄|○

元々のdbが壊れたのだろうか…でもPC上では動いたな。
そう言えば、Android端末を再起動したのが効いたのかな?
dbは後から列を加えたりしたので、Android上のsqliteが前のdbをキャッシュしてて、それを読み込んでいたのかな?

まあ何はともあれ、何とか解決して良かった(・∀・)
また時間を無駄にしてしまったが…_| ̄|○

※追記
もしかすると上記の予想で当たりかもしれない。同じ現象に遭遇したが、Androidを再起動して、アンインストール後にインストールし直したら無事に解決した。アンインストールも関係ないかも。 一回でもアプリを起動して、その中でdbを使った後、PC上でdbの中身をいじってから、Android端末を再起動していない状態でアプリを再インストールすると駄目だったから。

うにのトゲは刺さると痛い(´・ω・`)-9

音楽とかどうすんべか(´・ω・`)
俺氏は基本的にスマホゲーを音を出してやることがないので、余りスマホゲーにおけるBGMをあんまり重視していない。

加えてそもそも神経衰弱にBGMはなくてもいい気がしていたので、最初はBGMなしでもいいかなと思っていたが、やっぱり一応デフォルトのBGMだけは用意しておこうと思い直した。

だがしかし、俺氏、音楽センスが壊滅的にない(´・ω・`)

故にBGMとかどうしようかなと悩む。著作権フリーの無料素材でもいいかなと思ったが、何となく自動生成するソフトを探した。あるにはあったがWindowsアプリのものは高かった(´・ω・`)オカネナイン

仕方がないので登録が面倒くさいので回避していた音楽自動生成AIのWebサービスに手を出す。
最初にAmper Musicを試したが、設定が悪かったのか、やけに普通のイケてるっぽい音楽(?)みたいな感じのばかり出来てしまうので、Jukedeckの方も試してみる。どっちも似たような感じかなぁと思ったが、何となくBGMっぽい感じの音楽を生成出来たのでこれでいいやと適当に決めるw

 

ちなみにJukedeckで生成した音楽を無料で使用する場合は

Music from Jukedeck – create your own at http://jukedeck.com

というクレジット表記が必要らしい。著作権もJukedeckのモノになるらしい。まあAIに作ってもらっただけなので、それは別に構わないだけども、AIによる生成物の著作権って厳密にはどうなっていくのだろうか(´・ω・`)

それはともかく、大体俺氏が48枚セットで3分くらいかかるので、まぁ1分30秒の音楽をループさせておけばいいかなという感じに設定して生成したが、当然のことながら曲頭と曲終わりが綺麗に繋がるわけもなくw
致し方なく、曲終わりに3秒ほどの無音を挿入して調整してみたが、やっぱり不自然な感じは否めない(´・ω・`)シャアナイ

効果音については効果音ラボというサイトからかっぱらってきてもろてきた(・∀・)
ってよくよく考えみるとこのサイトは「Unityの寺子屋」で紹介されていたサイトだった(ノ∀`)モウワスレテタ


ゲーム作成には関係ないけれども、ぐぐってる途中で見かけたもので何となく面白そうだったもの。

Photo Musicという画像から音楽を生成してスライドショーにするというソフトは発想が面白かったな(・∀・)
フリー版を試しただけなので全機能についてはよく判らないけれども、各画像から生成された音楽を上手くつなぐような補完部分も生成してくれたら画像を1小節とかの単位でのパーツとしてブロックを組み立てるように出来るのになぁと思った。

Win専用の自動作曲・演奏ソフト【Deep Blue Juke】公開しました
こちらは生成物を出力するわけではないので試していないけど、面白いかもと思った(・∀・)
喫茶店とかでカスラックにウダウダ言われない為にはこういうBGM自動生成ソフトが必要になってきそう。


取り敢えず音楽関連はこんな感じでお茶を濁しておきたい。
本気でやろうとしたら悪い意味で絶対ハマるし(´・ω・`)

うにのトゲは刺さると痛い(´・ω・`)-8

なんちゃってDialogが一応出来た気がする(´・ω・`)

Dialogを実装しようとぐぐってみると、色々と実現方法はあったものの、ほとんどがCanvasを使ったりプラグインを追加してネイティブのDialogを呼び出すものであった為に

俺氏、全てを拒絶する(`・ω・´)イヤヤ

今考えてみると、素直にどっちかの方式に従っておけば、さっさと次の機能実装に取り掛かれたのであったが、俺氏は何故か

IMGUIで実装したかった(`・ω・´)
だっておら、onGUI()で実装出来たら楽だなって思ってたから(´・ω・`)ジツハギャクニメンドウクサカッタ

結果的に調べたり試行錯誤を繰り返したりで一日以上を費やしてしまった…_| ̄|○


IMGUIを使用したなんちゃってDialog
事の始まりはこちらのエントリ。
Unityゲームを終了する

この方法で取り敢えずBackButtonからのDialogっぽいモノを表示、GUI.Buttonをクリックしたらアプリケーションが終了するという形までは出来た。

ここで問題になったのは、ボタンクリック後、下のカードのTouchCard()が呼ばれてしまうこと(´・ω・`)
最初はクリックイベントが透過しちゃうのが原因なのかなと思って、EventSystem.IsPointerOverGameObjectでも使えばいいのかと考えたが、イマイチ使い方が解らない(´・ω・`)

参考にしたエントリのコード同様にGameManagerにbool型のisOpenedを作成して、TouchCard()内で

        if (GameManager.isOpened)
        {
            return;
        }

みたいな感じにしてるのに、なんか上手く行かない(´・ω・`)
凄く速くタッチすると下がめくれないことがあったりしたので、もしかしたらisOpenedがfalseになった瞬間にまだ指がタッチしていてそれが拾われちゃうのかとも思ったが、どうも必ずしもそうでもないような…(ヽ’ω`)ワカラナイ

で、まぁ色々悩んだ末に考えついたことは、
Delay入れれば、弾けるんじゃね(・∀・)?
といういい加減極まりない手法w


コルーチンによるボタンクリックアニメ

というわけでStartCoroutine+WaitForSecondsをクリック後に追加した。結果、isOpenedがfalseになるのが遅延して余計なクリックイベントを弾くようになった模様…実際にこの対処法が正しいのかは不明(´・ω・`)

参考:
Unityで一定時間待つ処理の実装方法
【Unity】スクリプトの処理の実行タイミングを操作する

で、ここでふと思ったのが、

WaitForSecondsを二回呼んで、その間にボタンのテクスチャを描き換えれば、ボタンクリックアニメっぽくなるんじゃね(・∀・)?
ということ。

ちょろっとやってみた。
それっぽくなった(・∀・) オレシカコイイ ダイテ(・∀・)


else節を利用したボタン押下アニメ
で、まぁ人の欲望とは際限なきもので、ボタンクリックアニメが出来るようになると今度はボタン押下状態のアニメというか画像変更がしたくなってくる(´・ω・`)タルコトヲシラヌ ヨクブカモノ

色々とぐぐっても、今ひとつGUI.Buttonの実体というか押下イベント状態を掴む方法が解らない(ヽ’ω`)

UnityのマニュアルでImmediate Mode GUI (IMGUI)の項を読む……

IMGUI システムは通常、プレイヤーが操作する普通のゲーム内ユーザーインターフェイスに使う事は意図されていません。それらにはゲームオブジェクトベースで考えられた Unity のメイン UI system を使ってください。そちらの方が UI 要素の編集と位置決めにゲームオブジェクトベースでアプローチでき、 UI のレイアウトと視覚デザインをするのにはより便利なツールです。

ガ━━(゚Д゚;)━━━ン!!

_| ̄|○

ぐぐってみても、そういうボタン押下状態の画像差し替えのコードはない…(ヽ’ω`)

実体もしくは押下イベント状態を掴む方法がないとなぁ…(´・ω・`)と半ば諦めかけていた時にふと思う。
GUI.Buttonって

if (GUI.Button (Rect (25, 25, 100, 30), "Button")){}

みたいな感じで書いて、クリックされている時は{}内のコードが実行される…
じゃあ、elseの時はどうなんの……

あ、ここでクリックではない時の実体(?)がつかめんじゃね(・∀・)?
と思う。

でまぁ、elseの部分でこのボタンのテクスチャやフォントサイズを変えればいいということに気付く。
具体的にはbool型のisPressedみたいな変数を追加し、

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            isPressed = true;
        }
        if (Input.GetMouseButtonUp(0))
        {
            isPressed = false;
        }
    }

で状態を格納するようにして、かつ、Input.mousePositionがボタンのRect内であるならばボタン押下状態とみなすという形でチェックし、trueならPressed状態、falseならNormal状態のGUIStyleをセットすることによって実現した(`・ω・´)

これが上手く動いた時は
俺氏天才(`・ω・´)
って思った。←こうやって自画自賛しないとやってられない人


面倒くさいので、サンプル用に削ったコードを作成せずそのまま載せるw

  1. Resourceフォルダの下にimagesフォルダを掘って、そこに以下の画像を突っ込む。押下状態の画像は通常状態の画像を反転しただけw Rect用のテクスチャ同様にコード内で生成してしまえば、完全にスクリプトだけで完結するなんちゃってダイアログが作れる。ちなみにアクアなボタン台 フリー素材のボタンをかっぱらってきて、ちょっと加工した(・∀・)


  2. GameManager側では
     
        void OnGUI()
        {
            float sw = Screen.width;
            float sh = Screen.height;
    
            if (GUI.Button(new Rect(0, 0, sw * 3 / 12, sh / 12), "menu") && !isOpened)
            {
                Debug.Log("push!");
                this.gameObject.AddComponent<MenuDialog>();
                isOpened = true;
                return;
            }
        }
    

    という感じでGUIボタンで呼び出す様にしたり、Androidの実機のBackボタンで試すなら

    #if UNITY_ANDROID
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                this.gameObject.AddComponent<MenuDialog>();
                isOpened = true;
                return;
            }
    #endif
    

    をupdate()に追加する。isOpened自体はこのサンプルでは本当は必要ないのだけれども、そこら辺を修正するのが面倒なのでどっかで宣言しておく。

  3. よく解ってないのだがY座標は画面下から上に増加していく座標のようなので、ボタン領域内のチェックはScreenheightから引いて逆転させている(´・ω・`)ココハジブンデモリカイシテナイw
    using System.Collections;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class MenuDialog : MonoBehaviour
    {
        private GUIStyle rectStyle = new GUIStyle();
        private GUIStyle quitStyle = new GUIStyle();
        private GUIStyle returnStyle = new GUIStyle();
        private GUIStyle cancelStyle = new GUIStyle();
    
        private float dialogWidth = 500;
        private float dialogHeight = 800;
        private float dialogLeft;
        private float dialogTop;
        private float buttonLeft;
    
        private float buttonWidth = 368;
        private float buttonHeight = 83;
        
        bool isPressed;
    
        void Start()
        {
            GUIStyleState rectStyleState = new GUIStyleState();
            rectStyleState.textColor = Color.white;
            rectStyleState.background = MakeRectTexture();
            rectStyle.normal = rectStyleState;
    
            SetTextConfig(rectStyle, 50, TextAnchor.MiddleCenter);
            SetTextConfig(quitStyle, 50, TextAnchor.MiddleCenter);
            SetTextConfig(cancelStyle, 50, TextAnchor.MiddleCenter);
            SetTextConfig(returnStyle, 50, TextAnchor.MiddleCenter);
        }
    
        Texture2D MakeRectTexture()
        {
            Texture2D rectTexture = new Texture2D(16, 16);
            for (int y = 0; y < rectTexture.height; y++)
            {
                for (int x = 0; x < rectTexture.width; x++)
                {
                    if (x == 0)
                    {
                        rectTexture.SetPixel(x, y, Color.white);
                    }
                    else if (x == rectTexture.width - 1)
                    {
                        rectTexture.SetPixel(x, y, Color.gray);
                    }
                    else
                    {
                        if (y == 0)
                        {
                            rectTexture.SetPixel(x, y, Color.gray);
                        }
                        else if (y == rectTexture.height - 1)
                        {
                            rectTexture.SetPixel(x, y, Color.white);
                        }
                        else
                        {
                            rectTexture.SetPixel(x, y, new Color(1f, 0f, 0f, 0.5f));
                        }
                    }
                }
            }
            rectTexture.Apply();
            return rectTexture;
        }
    
        void SetTextConfig(GUIStyle targetStyle, int fontSize, TextAnchor textAnchor)
        {
            targetStyle.fontSize = fontSize;
            targetStyle.alignment = textAnchor;
        }
    
        void MakeStyleNormal(GUIStyle targetStyle, Color color,Texture2D texture,int fontsize, TextAnchor textAnchor)
        {
            GUIStyleState tempStyleState = new GUIStyleState();
            tempStyleState.textColor = color;
            tempStyleState.background = texture;
            targetStyle.fontSize = fontsize;
            targetStyle.alignment = textAnchor;
            targetStyle.normal = tempStyleState;
        }
        
        bool CheckIsInButton(int y)
        {
            if (isPressed &&
                buttonLeft < Input.mousePosition.x &&
                buttonLeft + buttonWidth > Input.mousePosition.x &&
                Screen.height - (dialogTop + buttonHeight * y) > Input.mousePosition.y &&
                Screen.height - (dialogTop + buttonHeight * y + buttonHeight) < Input.mousePosition.y)
            {
                return true;
            }
            return false;
        }
    
    
        void OnGUI()
        {
            dialogLeft = Screen.width / 2 - dialogWidth / 2;
            dialogTop = Screen.height / 2 - dialogHeight / 2;
            buttonLeft = dialogLeft + dialogWidth / 2 - buttonWidth / 2;
    
            GUI.Box(new Rect(dialogLeft, dialogTop, dialogWidth, dialogHeight), "TestDialog", rectStyle);
    
            if (GUI.Button(new Rect(buttonLeft,
                dialogTop + buttonHeight * 2, buttonWidth, buttonHeight), "quit", quitStyle))
            {
                ChangeButtonStyleState(quitStyle, null, "images/quit2");
                StartCoroutine(DoDelayedMethod(quitStyle, Color.white, "images/quit", (0.1f)));
                Application.Quit();
            }
            else
            {
                if (CheckIsInButton(2))
                {
                    ChangeButtonStyleState(quitStyle, null, "images/quit2");
                }
                else
                {
                    ChangeButtonStyleState(quitStyle, Color.white, "images/quit");
                }
            }
    
            if (GUI.Button(new Rect(buttonLeft,
                dialogTop + buttonHeight * 4, buttonWidth, buttonHeight), "cancel", cancelStyle))
            {
                ChangeButtonStyleState(cancelStyle, null, "images/cancel2");
                StartCoroutine(DoDelayedMethod(cancelStyle, Color.white, "images/cancel", (0.1f)));
            }
            else
            {      
                if (CheckIsInButton(4))
                {
                    ChangeButtonStyleState(cancelStyle, null, "images/cancel2");
                }
                else
                {
                    ChangeButtonStyleState(cancelStyle, Color.white, "images/cancel");
                }
    
            }
    
            if (GUI.Button(new Rect(buttonLeft,
                dialogTop + buttonHeight * 6, buttonWidth, buttonHeight), "return", returnStyle))
            {
                ChangeButtonStyleState(returnStyle, null, "images/return2");
                StartCoroutine(DoDelayedMethod(returnStyle, Color.white, "images/return", (0.1f)));
                SceneManager.LoadScene("TitleScreen");
            }
            else
            {
                if (CheckIsInButton(6))
                {
                    ChangeButtonStyleState(returnStyle, null, "images/return2");
                }
                else
                {
                    ChangeButtonStyleState(returnStyle, Color.white, "images/return");
                }
            }
        }
    
        void ChangeButtonStyleState(GUIStyle targetStyle, Color? pressedFontColor, string FilePath)
        {
            GUIStyleState pressedButtonStyleState = new GUIStyleState();
            if (pressedFontColor != null)
            {
                pressedButtonStyleState.textColor = (Color)pressedFontColor;
            }
    
            pressedButtonStyleState.background = (Texture2D)Resources.Load<Texture>(FilePath);
            targetStyle.normal = pressedButtonStyleState;
        }
    
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                isPressed = true;
            }
            if (Input.GetMouseButtonUp(0))
            {
                isPressed = false;
            }
        }
    
        private IEnumerator DoDelayedMethod(GUIStyle targetStyle, Color? tempFontColor, string FilePath, float waitTime)
        {
            yield return new WaitForSeconds(waitTime);
            GUIStyleState tempButtonStyleState = new GUIStyleState();
            
            if (tempButtonStyleState != null)
            {
                ChangeButtonStyleState(targetStyle, tempFontColor, FilePath);
            }
            yield return new WaitForSeconds(waitTime);
            GameManager.isOpened = false;
            Destroy(this);
        }
    }
    

    参考:

    テクスチャの作成
    Textureを変更する


テクスチャの作り方を完全には理解していないので、その辺は参考サイトのコードを適当にいじって背景Rect用テクスチャを作った(ノ∀`)
背景Rect用テクスチャをきちんとした画像を用意して読み込んだり、setPixel()で頑張るかすれば、もっとちゃんとした外観になるはず。

テクスチャの透過はシェーダーをどうにかしないといけないとか面倒臭そうだったので四角いボタンにしたが、テクスチャを角丸画像にして背景色を背景Rect用テクスチャのものと同一にして多少のクリック判定の誤差を諦めるか、ボタン押下状態の背景Rect用テクスチャを用意してクリック時に同時に背景Rect用テクスチャも切り替えれば角丸ボタンも実装出来るんじゃないかなぁ( ゜σ・゚)ホジホジ ←今のところ、実装する気がない。

※2018/7/3追記
結局テクスチャをプログラムで生成する形にしたが、SetPixelでColor.clearをセットすると透過みたいになっている……ような気がする……がイマイチ状況を把握出来ていない(ノ∀`)

多分デリゲートとかをちゃんと理解している人ならもっとスマートな書き方が出来るのだろうが、俺氏には無理なので旧態依然とした因数分解的リファクタリングしか出来なかった(ノ∀`)


えらく長くなったが、これで今後このコードの意味がわからなくなっても、この経緯を読み直せば理解出来るはず( ・´ω・`)

何はともあれIMGUIとの戦いは勝ったわけでもないが

敗けなかった(`・ω・´) と思いたい。