カテゴリー: Unity

うにのトゲは刺さると痛い(´・ω・`)-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で出力した画像を加工したんだっけかな。

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


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

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

色々とハマっていたら、何を変更したか思い出せなくなって来た("・ω・゙)

結局、画面スクロールはやめた(ノ∀`)

取り敢えず今までのやり方で一応神経衰弱ゲームとしての機能は実装出来たのだけれども、実際に自分で何回か実機で試遊して思ったが、

 

 

 

神経衰弱と画面スクロールって、相性悪い_| ̄|○

スクロールするとそれが何行目で何列目かが判らなくなる(ノ∀`)
まあガイドみたいなのを表示すればいいんだろうけれども、そこまでするのもなんだなと。

ということで、World空間じゃなくてUI上にUI Image(GameObjectでもいいけども)を配置してスクロールしない一画面タイプにすることにした。この場合、解像度の違いによってカード間処理等を考慮しなくて済むような気がするし。

まあ画面スクロールは今後何かで必要になるかもしれないから、調査にかけた時間は無駄ではなかったと思いたい…(ヽ'ω`)


カードめくりはiTweenで

カード消失の前にカードめくりアニメーションを以下のページを参考に実装した。
【Unity Asset】iTweenがすごく便利だった
【Unity】iTweenの使い方 / お試し【入門】

上のエントリはiTween.RotateToの0.2の後のカンマがあることとcomplete()のgameobjectがgameObjectであることでエラーになった(´・ω・`)

実際のコード 裏側図柄のカードがタッチされた時に

                iTween.RotateTo(this.gameObject, iTween.Hash(
                    "y", 90, "time", 0.2f,
                    "oncompletetarget", this.gameObject,
                    "oncomplete", "CardOpenedAnim",
                    "delay", 0.01f
                ));

という感じでアニメを開始し、

上記のアニメが終わったら、つまり裏側画像を持ったImageが90度になったら、表側画像をImageにセットし、0度へと戻すようにした。

    void CardOpenedAnim()
    {
        this.gameObject.GetComponent<Image>().sprite = Resources.Load<Sprite>(filePathPrefix + FilePath);
        iTween.RotateTo(this.gameObject, iTween.Hash(
         "y", 0, "time", 0.2f,
          "oncompletetarget", this.gameObject,
          "delay", 0.01f
       ));
    }

今ひとつRotateの動きを理解していないが、何となくめくれてるように見えるのでいいかなぁ(・∀・)といういい加減な実装。後半部分の値を180度にするとカードの絵が鏡絵になってしまうし。fromを指定すればいいのか?

まあ、そもそも後半はアニメーションせずにそのまま表面を表示してしまってもいいのかもしれない。


途中、Editorで実行も出来るし、ビルド&ランも出来るのに、なぜかVS上ではiTweenに赤い波下線が出てCS0103エラーが表示される謎の現象が。後から作成したC#スクリプトファイル上では問題ないのに、元から有るファイル上でiTweenが見えないみたいな感じだった。

日本語コメントを削ったり、再起動しても解決しなかったので諦めて他のことをやった時だったか前のバージョンをチェックアウトしたら消えたけど、あれはなんだったんだろうか(´・ω・`)?