【Unity】マウスを使わずにキー入力で自動スクロールするInifiteScroll+α2

##・まずはじめに
- この記事は以前投稿した<br>
[【Unity】 テキストファイルを読み取り、自動でスクロールコンテンツを生成してくれるInfiniteScroll+α](https://qiita.com/Y0241-N/items/09e6ddc58f46296f72d0)<br>
を理解し、手順通りに作成していることを前提としています。いわば続きの記事に当たります。

　まだ見ていない方は、一度上記記事を見たうえで改めてこの記事を読み進めてください。

##・この記事を読んでできるようになること
- タイトル通り、以前の記事で作成したスクロールを、マウスを使わずにキー入力やコントローラーのスティック入力でこのように自動スクロールできるようになります。

![scroll50%.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/28957738-6490-d4cc-251b-40159ffbe2c8.gif)




- どのようにして移動を行っているか簡単に説明すると、<b> 範囲外のオブジェクトを選択した際に、<br>スクロールバーを一定値移動させる</b>という処理を行っています。

- それでは前回のプロジェクトを開き、早速実践してみましょう。

##Step1、プレハブ化したベースオブジェクトにButtonを追加する
- まず、前回プレハブ化したベースオブジェクトを開きます。プレハブを開くと、Hierarchyにプレハブ内部のオブジェクトが表示されるので、親オブジェクトである`GameObject`を選択し、右クリック>UI>Buttonを選択して追加します。![スクリーンショット (31).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/108b016c-8290-db03-059e-4928b076c55a.png)
- Buttonは追加されると一番下に生成されるので、このままではテキストが隠れてしまうため、Imageの下にドラッグして表示階層順を変更します。
 ![スクリーンショット (34).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/76db5719-0f45-e460-cbb8-78e709c04a0b.png)

- このままでは些か見栄えが悪いので、Buttonの見た目を変更します。まずは変更する画像をUnityに取り込みます、今回はこちらの画像を使用してください。
<br> 黒丸の横のPNG形式の透明な画像とハイライトっぽい光のバーの二枚をDLしてください。

- ＜![Invisible.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/f3c20603-5920-12a1-2626-a5b0134fcce3.png)＞←透明な画像

- ![Highlight-resize.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/44f57cce-5bba-d8f8-9223-1cbde5754ad5.png)

- 画像をDLしたら、DLした画像を選択し、UnityのResourcesフォルダにドラッグ＆ドロップします。<br>（画像の名前は適当に変更しても大丈夫です）
- ![スクリーンショット (37)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/485844b5-5473-27c6-25da-cf7cba53ac5a.jpeg)

- UnityではUIオブジェクトにテクスチャを割り当てるには、画像の`Textture Type`が<br>`Sprite(2D and UI)`である必要があります。 
- ![スクリーンショット (38)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/ec411368-a656-80da-d936-cac9bd34e88c.jpeg)

- この画像を、Buttonに適応するにはまず`Button(Script)`の`Transition`を`Sprite Swap`に変更します。すると、下部に以下のインスペクターが表示されます。
 - Highilighted Sprite (ボタン選択時に表示する画像)
 - Pressed Sprite (ボタンを押している間表示する画像)
 - Disabled Sprite (ボタンが反応しない状態で表示する画像(Interactableのチェックをはずした状態))
- ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/f2e3fc2b-032e-f94f-e167-e467362a2239.png)
- 一度画像の位置を調整する為に、`Image(Script) `の`Source Image`にハイライトの画像を割り当て、適当な位置に調整しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/c6ebfe69-0498-14ec-c53b-5bd0e71ca402.png)
調整が完了したら、`Source Image `には透明な画像を割り当て、`Highilighted Sprite `にハイライトの画像を割り当てます。これで通常時は何も表示されず、選択された際にハイライトの画像が表示されるようになりました。
さらに`Button(Script)`の`Navigation`を`Vertical`にしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/6d8908a1-68c1-1039-743b-26c4c00427da.png)

##Step2、Scriptを追加し、自動スクロールのための準備を行う
- 次にScriptを作成し、ベースの親オブジェクト（MyItem.csが付いている）にアタッチします。
`ButtonComopnent`のインスペクターには先ほど作成したButtonをドラッグ＆ドロップしてください。

<details><summary><b>CheckPos.cs　（click here) </b></summary><div>

```C#:CheckPos.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class CheckPos : MonoBehaviour
{
    public Button buttonComponent = null;
    GameObject P;
    GameObject P2;
    GameObject P3;
    GameObject Scroll;

    ScrollRect ScrollRect;

    public static int co;
    static int u;
    static int d;

    void Start()
    {
        P = transform.parent.gameObject;
        P2 = P.transform.parent.gameObject;
        P3 = P2.transform.parent.gameObject;
        Scroll = GameObject.FindWithTag("Player");
        ScrollRect = Scroll.GetComponent<ScrollRect>();        
    }

    public void Update()
    {
      if(Input.GetKeyDown(KeyCode.Z))
        {
            Debug.Log(EventSystem.current.currentSelectedGameObject.GetComponent<RectTransform>().position);
        }

        if(Input.GetKeyDown(KeyCode.F))
        {
            if(this.gameObject.name == "0")
            {
                buttonComponent.Select();
            }
        }

        if(P3.activeSelf == true && co == 0)
        {
            if(this.gameObject.name == "0")
            {
                buttonComponent.Select();
            }
            co++;   //coは一度だけ選択を合わせるために使用している、これがないとUpdateなので、常に選択され続ける。
        }
    }
}
```
</div>　</details>

<details><summary><b>スクリプトの説明（click here) </b></summary><div>
>```C#
Scroll = GameObject.FindWithTag("Player");
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/c423ffdc-ccc9-ff3e-2764-d79b54cf39f1.png)

- `ScrollRect(Script)`がアタッチされているオブジェクトを取得するために、`Tag`を設定する必要があります。<br>
`Tag`は`AddTag`から自由に追加できます。<br><br>今回は既存のTagを使用していますが、既存のTagはAseetsなどで使用していたり、配布されているスクリプトで使用されている可能性が高いため、AddTagで追加した方がよいです。

>```C#
      if(Input.GetKeyDown(KeyCode.Z))
     {
        Debug.Log(EventSystem.current.currentSelectedGameObject.GetComponent<RectTransform>().position);
     }
```

- Zキーを押すと、現在選択されているUIオブジェクトのワールド座標をDebug.Logに表示する。<br>
これが非常に重要、ローカル座標だと生成されるコンテンツの座標はすべて同じ数値で帰ってくるため、ワールド座標を取得する必要がある。<br>
この値のY座標を使って範囲外に出たかどうかを判定する。

>```C#
     if(Input.GetKeyDown(KeyCode.F))
     {
        if(this.gameObject.name == "0")
        {
           buttonComponent.Select();
        }
     }
```

- Fキーを押すと、一番上のボタンをフォーカスする。これはマウスで画面をクリックしてしまうと選択状態が解除されてしまうので、そうなったとき様です。

>```C#
      if(P3.activeSelf == true && co == 0)
      {
          if(this.gameObject.name == "0")
          {
              buttonComponent.Select();
          }
          co++;   //coは一度だけ選択を合わせるために使用している、これがないとUpdateなので、常に選択され続ける。
      }
```

- 画像のP3がアクティブかつカウントが０の時、オブジェクトの名前が「０」のオブジェクトの　　Buttonをフォーカスしてカウントに「+1」するという処理。<br>
- この「co」を表示切替などのタイミングで「co = 0 ;」とすることで、再度P3がアクティブになったときに自動デフォーカスさせることができる。<br>
![スクリーンショット (39).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/e8571943-3b89-915d-4a57-e2704308db40.png)

</div>　</details>
<br>
**~ アタッチした状態 ~**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/e592f149-774f-1d80-6f6d-1df3fe63ff3c.png)

- 準備が完了したら、シーンを再生します。<br>見える範囲の一番上と一番下のオブジェクトの座標が知りたいので、一番上と下で`Zキー`を押します。

- すると左下に座標が表記されるので、この値は（x,y,z）の順に並んでいるため、真ん中の値を記録しておきます。<br>
今回の場合は上が`640.9`で下が`230.9`ですね。 
 ![rectpos.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/620ec345-4ff3-7d52-2422-52aa81c1334e.gif)

##Step3、範囲を定め、自動スクロールさせるスクリプトを用意する
では先ほど記録した値を使用するスクリプトを作成します。
このスクリプトは`CheckPos.cs`と入れ替えでアタッチします、なので、このスクリプトを親オブジェクトにアタッチしたら`CheckPos.cs`はRemoveComponentしてください。
<details><summary><b>SelectIf.cs　（click here) </b></summary><div>

```C#:SelectIf.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class SelectIf : MonoBehaviour
{
    [SerializeField] public Button buttonComponent = null;
    GameObject P;
    GameObject P2;
    GameObject P3;
    GameObject Scroll;

    ScrollRect ScrollRect;

    public static int co;
    float pos;
    static int u;
    static int d;

    void Start()
    {
        P = transform.parent.gameObject;
        P2 = P.transform.parent.gameObject;
        P3 = P2.transform.parent.gameObject;
        Scroll = GameObject.FindWithTag("Player");
        ScrollRect = Scroll.GetComponent<ScrollRect>();

        pos = 1f/( (float)MyItem.rowLength - 5f);
        
    }

    public void Update()
    {

        if(P3.activeSelf == true && co == 0)
        {
            if(this.gameObject.name == "0")
            {
                buttonComponent.Select();
            }

            co++;   //coは一度だけ選択を合わせるために使用している、これがないとUpdateなので、常に選択され続ける。
        }
                
        if(EventSystem.current.currentSelectedGameObject != null)
        {
            if(EventSystem.current.currentSelectedGameObject.GetComponent<RectTransform>().position.y >= 645 )
            {
                ScrollRect.verticalNormalizedPosition = ScrollRect.verticalNormalizedPosition + pos ;

            }else if( EventSystem.current.currentSelectedGameObject.GetComponent<RectTransform>().position.y <= 210 )
            {
                ScrollRect.verticalNormalizedPosition = ScrollRect.verticalNormalizedPosition - pos;
            }
        }
    }
}
```
</div>　</details>

<details><summary><b>スクリプトの説明（click here) </b></summary><div>
>```C#
pos = 1f/( (float)MyItem.rowLength - 5f);
```

この`pos`が範囲外の物を選択した際に、「どれだけスクロールするか」の値になっています。
式の中身としては【 １ ÷（ 要素の総数 - 表示可能な要素の数）】→【 １÷見えない範囲にある要素の数 】
<br>

- 「１」はスクロールバーの移動が0～1の間で管理されているため、その総数である１となる。
 
- 「要素の総数」はスクロールコンテンツとして登録する総数、つまりテキストファイルの中身。今回は「15」となる。この値は自動的に登録されるので手動で変更する必要なし。
 
- 「表示可能な要素の数」はスクロールビューで定めた範囲内に収めることのできる要素数なので、今回は「5」となる。この値は当然何個要素を見えるようにするかで変わってくるため、適宜自分で調整してください。

>```C#
        if(EventSystem.current.currentSelectedGameObject != null)
        {
            if(EventSystem.current.currentSelectedGameObject.GetComponent<RectTransform>().position.y >= 645 )
            {
                ScrollRect.verticalNormalizedPosition = ScrollRect.verticalNormalizedPosition + pos ;
            }else if( EventSystem.current.currentSelectedGameObject.GetComponent<RectTransform>().position.y <= 210 )
            {
                ScrollRect.verticalNormalizedPosition = ScrollRect.verticalNormalizedPosition - pos;
            }
        }
```

`position.y >= 645 `と`position.y <= 210 `

この部分が範囲となっています、先ほど`CheckPos.cs`で確認した値をもとに調整を行います、上は数値に+、下は数値に-をしてください。この値はかなりシビアで、シーンを再生して挙動を確認しながら調整してください。

もちろん、この値は当然スクロールビューのサイズを変えれば前提の値が変わってくるので、適宜確認して設定してください。
![枠.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/25f12a3c-ef3a-6b95-b056-28dec27e4dba.png)


</div>　</details>

さて、これでおおよその準備は整いましたが、**以下の設定が正しくないとうまく機能しません。**
（筆者はこの設定が異なっていることに気付かず、再現性が得られず相当悩みました）

##Step4、設定の確認（<font color="red">重要</font>）
- 主に確認することは、各オブジェクトの`Rect Transform`の`Anchor Presets`です。
ここの設定が異なっていると、この機能は**<font color="red">壊滅</font>**します。

- まずは**プレハブ化してあるベースオブジェクト**の設定を確認します。
 <br>`center&top`に設定し、Postionの値は全て0、WidthとHeightの値は子のオブジェクトによって左右されます。今回はおそらく画像と同じ値になっていると思います。ここのHeightの値は後に使用します。

　さらに`Pivot`の値が`X:0.5 Y:1`となっていることを確認します。
　この値はオブジェクトを操作する際に基準となる値であり、Yを1にすることで頂点を基準にしています。

　`Rect Transform`の設定について詳しく知りたい方は、tsubaki様のこちらの記事を参考にしてください。
　[UnityのuGUIのレイアウト調整機能について解説してみる（RectTransform入門）](http://tsubakit1.hateblo.jp/entry/2014/12/19/033946)
　![ベースオブジェクトの設定2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/3a3fc5ae-93d3-39de-ed06-d91d78e5e41b.png)
![ベースオブジェクトの設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/bf5a6cc6-618d-5ea1-162e-c5d847bb950a.png)

- 次に、**`Scroll Rect(Script)`が付いているオブジェクト**の設定を確認します。
　<br>このオブジェクトのAnchorは特にどれでも問題ありません。`middle&center`が無難です。
　<br><br>**重要な部分**はこのオブジェクトのHeightの部分が、<br>`ベースオブジェクトのHeight × 表示可能な要素の数 + 1`となっていることです。
このオブジェクトが表示可能な要素の数を決めているので、今回は５つ見えるようにしているため`201`としています。<br><br>+1する理由ですが、`200`ぴったりで試していただくとわかると思いますが、一番下まで移動して上に戻る際に途中で止まります。<br>原因はいまいちよくわかっていませんが、恐らくスクロールさせるposのfolatとしての小数点数が関係しているんじゃないかなぁと思っています。
![スクリーンショット (35).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/0ee45b19-fb02-9b34-38fa-d77d104a509b.png)

- 最後に**シーンを再生した際にベースオブジェクトをまとめる役割のオブジェクト**の設定です。
 <br>`Anchor`と`Pivot`の設定はベースオブジェクトと同じです。`Height`の値は親である**`Scroll Rect(Script)`が付いているオブジェクト**と同じ値に設定します。

　そして、`InfiniteScroll (Script)`の`instantateItemCount`は**表示可能な要素の数+3**です。
<br>　理由としては、Maskをはずして動作を確認すると勘付く方もいると思いますが、スクロールの際に範囲外の<br>　部分で次に選択するオブジェクトが生成され、1つ分移動した際にもう１つ分新たに生成する...という風に<br>　なっています。なので、上下に生成分+2と余白+1で+3という理由です。

<details><summary><b>+2と+3の違い（click here) </b></summary><div>
+2の場合（下までスクロールできない）　　　　　　　　　　　　　+3の場合（問題なく動作する）
![+2の場合resize.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/d5ead4fa-a352-aa9a-3206-fcc4050dae95.gif)　　　　　　　　　　　　　![+3の場合resize.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/10ce7783-e1d2-761c-d3ed-b1bc9328160a.gif)
</div>　</details>
![コンテンツまとめの設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/bdfcb7e0-47b9-d1c4-203d-52bb984e7f00.png)

##Step5、シーンを再生し、動作を確認する
- ここまでの手順を**<font color="red">間違いなく</font>**こなしていれば、シーンを再生するとこの記事のTopにあるGIFと同じ挙動になるはずです。

- これでテキストファイルの中身を変えれば好きな文字列を表示でき、さらにButtonコンポーネントを使用しているので、スクリプトで`swtich (transform.name)`を使えば、ボタンごとに機能を割り当てることも可能です。おそらく、さらに拡張して使うこともできると思います。

##・さいごに
- ここまで読んでいただき、ありがとうございました。お疲れ様です。
<br> ここからは筆者の自分語りになるので、興味のない方はこの記事を閉じてください。

<br>
<br>

- まずこの記事を作成しようと思った理由ですが、ひとえに「便利な物ができたからみんなにも使ってほしい！」という思いからまとめました。<br>思っていた以上に時間が掛かりましたが、とにかく丁寧にわかりやすく、見やすい記事を心がけたつもりです。今後誰かの役に立てば幸いです。
<br>

- さて、ここから先は本格的な身の上話になりますが、筆者は2019/05からUnityとC#の勉強を始め、現在三ヶ月目です。それまではスクリプトは一度ノベルゲームを作りたいと思い、Javaを勉強しようとしましたが訳が分からず諦めたぐらいで、一切の知識はありませんでした。
 <br>C#の基礎の部分をpaizaラーニングで学び、そこから先はUnityで実際にやりたいことを実現するにはどうすれば可能かをネットで調べながら試行錯誤を繰り返していきました。<br>その中で、何度も先人の知恵に助けられ、支えられてきました。<br><br>もしもこの先人の知恵がなく、自分一人で0から積み上げていかなければならなかったら、挫折していたでしょう。この経験を経たからこそ、私も誰かの役に立つような記事を遺したいという思いが生まれました。<br><br>なにかを学ぶことはとても大変なことです、たった一人では諦めたくなることもあるかもしれません。<br>ですがそんなときに、誰かの知恵を頼ることができれば、道は閉ざされずに済むかもしれません。
<br>そういうわけで、長々と語りましたが、今後もなにか役立ちそうなことを見つけたら記事にしたいと思います。
<br>ここまで読んでくださった方は本当にありがとうございました、貴方も誰かの力になれますように。