雨小屋-あまごや-

数値入力ピッカー

制作中のゲーム用に数値入力ピッカーが欲しいなと思ったものの、有償アセットを買うのはちょっと厳しく(忘れた頃にやってきた保険の更新……)、何かないかと調べるも、無限スクロールはあるけどピッカーのように特定の位置に止められるのは見つからない……

と、いうわけで、自作することにしました。


▽サンプル

慣性とかはないので、動きに改良の余地はまだまだあると思いますが、こんな感じで上下に無限スクロールするピッカーです。


▽作り方

ゲームオブジェクトはこんな感じ。

Text0~18がスクロールする数字です。
もう少し減らせたらと思いつつも、とりあえず保留。

サンプルでは、このプレハブを5つ並べて使っています。
また、上下の白いグラデーションも、並べたプレハブの上に被せてます。
グラデーションについては、UI Gradientを使用させていただいています。

ソースはこんな感じ。


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using TMPro;

public class NumberPickerManager : MonoBehaviour, IEndDragHandler
{
    // 定数
    public float PICKER_NUM = 19; // 数値ピッカーの要素数
    private float DEFAULT_POSITION = 0.5f;    // スクロールの定位置
    public int MAX_NUM = 9;    // ピッカーの数値の最大値

    // オブジェクト参照
    public GameObject contentsParent; // 数値ピッカーの数値要素の親オブジェクト

    // メンバ変数
    public int currentNum = 0; // 現在の数値ピッカーの値
    private int centerNum;  // 数値ピッカーの中で中央に来るオブジェクトの順番。PICKER_NUM / 2 を四捨五入した値

    // Start is called before the first frame update
    void Start()
    {
        this.GetComponent().verticalNormalizedPosition = DEFAULT_POSITION;
        centerNum = Mathf.RoundToInt(PICKER_NUM / 2);
    }
    
    // ドラッグ終了時
    public void OnEndDrag(PointerEventData data) {
        // 中央からの移動距離を求める
        float pos = Mathf.Clamp(this.GetComponent().verticalNormalizedPosition, 0, 1);
        int move = Mathf.RoundToInt((pos - DEFAULT_POSITION) * PICKER_NUM * -1);

        // 数値ピッカーを移動後の状態に更新する
        UpdatePickerNum(currentNum + move);
    }

    // ピッカーに指定した値を設定する
    public void UpdatePickerNum(int value) {
        currentNum = AdjustNumber(value);
        // 移動後の値に各要素のtextを書き換える
        for (int i = 0; i < PICKER_NUM; i++) {
            contentsParent.transform.GetChild(i).gameObject.GetComponent().text = 
                    AdjustNumber(currentNum + i + 1 - centerNum).ToString();
        }
        // スクロール位置を初期位置に戻す
        this.GetComponent().verticalNormalizedPosition = DEFAULT_POSITION;
    }

    // 各要素に設定する数値を範囲内に収める
    private int AdjustNumber(int num) {
        while(num > MAX_NUM) {
            num -= (MAX_NUM + 1);
        }
        while(num < 0) {
            num += (MAX_NUM + 1);
        }
        return num;
    }
}

				

大体ソース内のコメントに書いた感じなのですが、まとめると、

 ・ピッカーの親オブジェクト(NumberPicker)のスクロール位置は、
  静止時には常に中央(0.5)固定

 ・ドラッグ終了時に、中央(静止時の位置)から上下にどれぐらい動いたかを取得して、
  移動後の数値を算出

 ・移動後の数値が中央に表示されるように、Text0~18の各textを書き換える

みたいなことをしてます。

応用として、MAX_NUMの値を23とか59とかに変えると、時刻入力ピッカーも作れるはず(要オブジェクトの横幅調整)

AdjustNumber()内にそのまま書いちゃってるnum < 0の0をMIN_NUMとかにでもして、num -= (MAX_NUM - MIN_NUM + 1)、num += (MAX_NUM - MIN_NUM + 1)とかに修正したら1~9とかのピッカーにもできるはず。

あと、数値を配列の添え字として使えば、文字列の選択ピッカーにすることも多分できると思います。

ところで、ここまで書いてソース読み返して思ったのですが、PICKER_NUMのNUMは他のNUMが大体数値なのに対して個数なので、命名失敗したかも。
命名難しい……


▽参考

【Unity】ドラムロール入力の実装