[Unity]SRDebuggerのオプションにSliderを実装する

# はじめに

本記事は Craft Egg Advent Calendar 2022 1日目の記事です
https://qiita.com/advent-calendar/2022/craftegg

【12/1】[Unity]SRDebuggerのオプションにSliderを実装する(本記事)
【12/2】[高校生からのゲーム制作についての質問に答えたよ](https://note.com/arumani/n/n218a1a231c2a)


# SRDebuggerとは
UnityAssetStoreで販売されている、実機上でコンソールログや簡易プロファイラーなどを開けたり、自作のデバッグ機能を実行したりすることができる有料アセットです。
Unityへの導入方法や各種機能の説明などは、色々な方の記事が既にたくさんあるため今回は省略します。

※こちらの方の記事が見やすくてオススメです
https://qiita.com/tomori_hikage/items/a2372333f8b73b421113

AssetStore上のSRDebuggerページリンクはこちらから
[SRDebugger - Console & Tools On-Device](https://www.assetstore.unity3d.com/jp/#!/content/27688)
![thumbnail.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/17a88621-6c61-ce93-8a2c-0c8e24ee62e6.jpeg)

# Sliderが表示できない
SRDebuggerのOptionsには、プロパティの型に応じたUIが表示されます。
整数型・浮動小数点数値型のプロパティを指定したときは、左右にボタンが表示され数値を増減させて調整する事ができますが、スライダーを使って直感的に値を調整したいときがあります。

しかし、SRDebuggerにはOptionsのUIにSliderを表示する機能が備わっていないため、デフォルトのままでは使用することができません。
今回は **【整数型・浮動小数点数値型のプロパティを定義したとき、Sliderを表示させたいときのみ表示させる】** を目標に拡張してみました。

![before0.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/eaaac009-2bd8-0499-77fa-ed7157783573.jpeg)
![before1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/96124860-7c0e-917c-db55-c411f6114670.jpeg)


# 検証環境
```
Windows 10
Unity 2021.3.2f1
SRDebugger 1.12.1
```

# 実装方法について
SRDebuggerのOptions画面でプロパティに応じて表示されるUIは、`Assets/StompyRobot/SRDebugger/Resources/SRDebugger/UI/Prefabs/Options`フォルダにPrefab形式で存在しており、Optionsタブを選択した際にコードに定義されているプロパティの情報から使用するPrefabをロードして生成しています

整数型・浮動小数点数値型のプロパティの場合は `NumberOption.prefab` が生成されるので、今回はこのPrefabにSliderを追加し、関連スクリプトにSlider周りの処理を追加することで実装していきます
![edit0.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/b885a0bf-2854-ee63-e87d-e3602c1e923a.jpeg)

## Slider属性を追加定義する
SRDebuggerでは、`DisplayName, NumberRange` など独自定義されている属性(Attribute)があります
定義場所は下記の2箇所で、そこにSlider属性を画像のように定義します

`Assets\StompyRobot\SRDebugger\Scripts\SROptions.Attributes.cs`
![edit1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/a3981846-d1b1-6aba-8401-7042b20ab4b3.jpeg)

`Assets\StompyRobot\SROptions\SROptions.Attributes.cs`
![edit2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/64afbd8d-2a10-1450-5620-f3de41fa5bf4.jpeg)

## 整数型・浮動小数点数値型で使用されるUIクラスにSliderを使用したときの処理を追加する
NumberOption.prefabには `NumberControl` クラスがアタッチされており、このクラスの中でプロパティに付与されている属性の判別や、ボタンUIのオンオフなどを行っています

このクラスにSlider関連の処理を追記していきます
※量が多いのでクラスコードから省略形式で抜粋。コメントが付いている箇所が該当部分です

```csharp:Assets\StompyRobot\SRDebugger\Scripts\UI\Controls\Data\NumberControl.cs
using System.Globalization;

namespace SRDebugger.UI.Controls.Data
{
    using System;
    using System.Collections.Generic;
    using SRF;
    using SRF.UI;
    using UnityEngine;
    using UnityEngine.UI;

    public class NumberControl : DataBoundControl
    {
        ~~~~~~~~省略~~~~~~~~
        /// <summary>
        /// スライダー拡張追加用
        /// </summary>
        [SerializeField] private Slider _numberSlider;

        protected override void Start()
        {
            base.Start();
            NumberSpinner.onEndEdit.AddListener(OnValueChanged);
            // スライダー操作時のコールバック登録
            _numberSlider.onValueChanged.AddListener(OnValueChanged);
        }

        private void OnValueChanged(string newValue)
        {
            try
            {
                var num = Convert.ChangeType(newValue, _type, CultureInfo.InvariantCulture);
                UpdateValue(num);
                // スライダー上で操作した時に値が反映されないのでその対応
                Refresh();
            }
            ~~~~~~~~省略~~~~~~~~
        }

        /// <summary>
        /// Slider操作時の値をProperty側に反映するための関数
        /// </summary>
        private void OnValueChanged(float newValue)
        {
            OnValueChanged(newValue.ToString());
        }

        protected override void OnBind(string propertyName, Type t)
        {
            ~~~~~~~~省略~~~~~~~~
            // Slider拡張分
            var sliderAttrib = Property.GetAttribute<SliderAttribute>();

            if (sliderAttrib != null)
            {
                NumberSpinner.MaxValue = Math.Min(sliderAttrib.Max, NumberSpinner.MaxValue);
                NumberSpinner.MinValue = Math.Max(sliderAttrib.Min, NumberSpinner.MinValue);

                _numberSlider.maxValue = (float)NumberSpinner.MaxValue;
                _numberSlider.minValue = (float)NumberSpinner.MinValue;

                _numberSlider.wholeNumbers = NumberSpinner.contentType == InputField.ContentType.IntegerNumber;
            }
            else
            {
                var rangeAttrib = Property.GetAttribute<NumberRangeAttribute>();

                if (rangeAttrib != null)
                {
                    NumberSpinner.MaxValue = Math.Min(rangeAttrib.Max, NumberSpinner.MaxValue);
                    NumberSpinner.MinValue = Math.Max(rangeAttrib.Min, NumberSpinner.MinValue);
                }
            }
            ~~~~~~~~省略~~~~~~~~

            if (DisableOnReadOnly != null)
            {
                foreach (var childControl in DisableOnReadOnly)
                {
                    childControl.SetActive(!IsReadOnly);
                }
            }

            // 上記のforeach文でスライダー属性が無い場合でも、readonlyで無い場合はスライダーが表示されてしまうので
            // ここで再度属性チェックを入れてアクティブ切り替えを行っている
            _numberSlider.gameObject.SetActive(sliderAttrib != null);
        }

        protected override void OnValueUpdated(object newValue)
        {
            var value = Convert.ToDecimal(newValue, CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture);

            if (value != _lastValue)
            {
                NumberSpinner.text = value;
                // スライダーがアクティブの場合はスライダー側にも値を反映させる
                if (_numberSlider.gameObject.activeSelf)
                {
                    _numberSlider.value = float.Parse(value);
                }
            }

            _lastValue = value;
        }
        ~~~~~~~~省略~~~~~~~~
    }
}
```

## 整数型・浮動小数点数値型で使用されるUIPrefabにSliderを追加する
最後に `NumberOption.prefab`にSliderを追加して、NumberControlに各パラメーターを設定したら完了です
![edit3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/772cf75b-e2a3-e1e2-9a1e-53e4361f6465.jpeg)
![edit4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/d8440fb2-5b44-735d-1f0b-244ab1afa2a8.jpeg)

# 動作確認

整数・浮動小数点型のプロパティに対して **[Slider(min, max)]** という属性を付与すると、SliderのUIが表示されるようにしました

コード(Slider属性を追加)
![after.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/31a78424-a8a0-df0f-e152-e4749571a872.jpeg)
オプション画面
※スライダーを追加しただけなので、左右ボタンタップや数値直接入力も使用可能
![result.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332769/7691a366-fbb2-aa00-816b-b5b4893205fe.gif)

# Slider実装時の注意点
【実装方法について】の部分で気づかれた方もいると思いますが、SRDebuggerの内部で行われているプロパティの型に応じてUIを決める処理の都合上、今回の実装は**SRDebuggerの内部コードとPrefabに手を加えて実装しています**
このため、SRDebuggerのアップデート時には今回実装した内容が全て破棄されてしまうので、アップデート後に再度Sliderの拡張実装をする必要があります

また、Sliderを追加するにあたり既存のUIだと窮屈だったので縦幅を少し広げています。そのため**Slider追加前のUIと若干見た目に差異が出てしまいます**
(窮屈でも良い場合は広げなくても実装自体は可能です)

# まとめ
普段何気なく使っていたSRDebuggerですが、内部コードやPrefabまで見る機会はなかったので、良い勉強になりました

今回SRDebuggerにSliderを追加する事が出来ましたが、上記の注意点にもある通りデメリットも発生するような形になってしまったのが少し心残りとなりました
しかし、ツールやデバッグ機能等でSliderが使えるのは少なからずメリットでもあるので、もしSRDebugger上でSliderを使いたいという事があれば、この記事を参考にしていただければ幸いです