カテゴリー: C#

うにのトゲは刺さると痛い(´・ω・`)-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との戦いは勝ったわけでもないが

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

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

なんか色々やってたら、何やったか忘れてしまった(´・ω・`)

  • 取り敢えずiTweenをやめて、DTweenを導入した。
  • ヒットしなかった場合のカードの戻りを追加した。
  • ヒットした場合の雑な消滅アニメを追加した。
  • 24枚、36枚、48枚のパターンを作った。
  • 雑なタイトル画面を作った。
  • admobを追加した。
  • Backボタンによるアプリケーションの終了を追加した。
  • Dialogをどうにかしようとしている

くらいかな?

24枚、36枚、48枚でカードサイズを変えた結果、48枚パターン以外では拡大表示が入らなくなったり、配布カードの上にかぶることが判明。まあ24枚と36枚のパターンだとカードの値が見えにくいということもないので、拡大表示しなきゃいいかなぁという結論に。

参考:
DOTween関連
[Unity]DOTweenめーも

[Unity]インスタンス生成時に引数を渡したい

Unity : モバイル画面の方向を固定、あるいは変化を検出

[Unity] AdMob 広告をAndroidに実装する
Android Admobで使うデバイスIDの取得方法(Unity)


ぐるぐる回転

画像を裏返すようなアニメーション
このコードを理解せずに適当に延々と回転し続けるアニメーションを作ろうとしてハマる(ヽ'ω`) 最初の絵が二周目から反転してしまう… そもそも仕組みもこのコードの挙動も理解していないので原因が分からず、最終的に"89"と"-89"にすると反転が起こらず、かつ人間の目には90を指定した時との差はわからない感じだったので、それで良しとした(ノ∀`)


カード裏面

何となくカード裏面も変更出来たらいいのかしらと思いつつ、絵を描くと何故か謎のクリーチャーをこの世に爆誕させてしまう俺氏の才能が怖いので、フラクタルみたいな図柄を自動生成してくれるソフトを探す。最初はApophysis.7X16というソフトを使ってみたのだけれども、あんまり使い方が判らず(´・ω・`)

その後、juxのフリー版を落として来て、色々と触ってたら、そこそこそれっぽい画像が出来たので適当に加工してカードにはめてみた。なんか神々しいというかRPGのラストとかに出て来そうな感じの画像が結構出来たw 原理等は全くわからないけど凄いと思いました(・∀・)(小並感)

でもトランプの裏面って左右対称上下対称が普通だったんだっけか。まあただの神経衰弱ゲームなので細かいことはキニシナイ(・3・) あ、一番最後のはApophysis.7X16で出力した画像を加工したんだっけかな。

ちなみにこの作業で半日以上を潰した(ノ∀`)
理屈が解らないので適当に数値を替えてやっているのはあたかもひたすらガチャを引き続ける人のようであった…


まだまだやることが山程あるな…(ヽ'ω`)