0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】Vetor3用のInputFieldを作る

Posted at

概要

Vector3や配列等の複数の要素を持つ型に使用できるInputFieldが必要だったため、汎用的に使用できそうなものを作成した.カスタムInputFieldは、入力値を内部でReactivePropertyに変換して管理し、外部へはその変換後の値を通知する.また、Modelとの連携を容易にするためのインタフェースを実装しておく.

実装の詳細

インタフェース定義
ゲッタ/セッタと通知用Observableを持つインタフェース.

IDataHolder.cs
namespace nitou {

    /// <summary>
    /// <see cref="T"/>型のデータを保持できるオブジェクト.
    /// </summary>
    public interface IDataHolder<T> {
        public IObservable<T> OnValueChanged { get; }
        // ※必要に応じてバリデーションを差し込む
        public T GetValue();
        public void SetValue(T value);
    }

    public static class DataHolderExtensions {

        public static void BindTo<T>(this IReactiveProperty<T> property, IDataHolder<T> target, ICollection<IDisposable> disposables) {

            // Property → Target
            property.SubscribeWithState(target, (x, t) => t.SetValue(x)).AddTo(disposables);

            // Targer → Property
            target.OnValueChanged.SubscribeWithState(property, (x, p) => p.Value = x).AddTo(disposables);
        }
    }
}

基本のInputFieldビュークラス

InputFieldView.cs
using System;
using UniRx;
using UnityEngine;

namespace nitou.UI{
    public abstract class InputFieldView<T> : MonoBehaviour, IDataHolder<T>{

        protected readonly ReactiveProperty<T> _valueRP = new();
        public IObservable<T> OnValueChanged => _valueRP;

        // LifeCycle Events
        protected virtual void OnDestroy() {
            _valueRP.Dispose();
        }

        // Public Method
        public virtual T GetValue() => _valueRP.Value;
        public virtual void SetValue(T newValue) => _valueRP.Value = newValue;
        public virtual void ResetValue() => _valueRP.Value = default(T);
    }
}

Vector3用のカスタムInputField

※入力文字列のバリデーションは別途InputFieldインスペクタで設定する想定.

Vector3InputFieldView.cs
using System;
using System.Globalization;
using UniRx;
using UnityEngine;
using TMPro;

namespace nitou.UI {

    public sealed class Vector3InputFieldView : InputFieldView<Vector3> {

        [Header("View")]
        [SerializeField] private TMP_InputField _xInputField;
        [SerializeField] private TMP_InputField _yInputField;
        [SerializeField] private TMP_InputField _zInputField;

        [Header("Settings")]
        [Range(0, 6)]
        [SerializeField] private int _decimalPlaces = 2;


        /// ----------------------------------------------------------------------------
        // LifeCycle Events

        private void Awake() {
            // ViewModelの監視
            _valueRP.Subscribe(v => ApplyValue(v)).AddTo(this);

            // Viewの監視
            Observable.Merge(
                _xInputField.onEndEdit.AsObservable().AsUnitObservable(),
                _yInputField.onEndEdit.AsObservable().AsUnitObservable(),
                _zInputField.onEndEdit.AsObservable().AsUnitObservable()
            )
            .Subscribe(_ => UpdateValue())
            .AddTo(this);
        }


        /// ----------------------------------------------------------------------------
        // Private Method

        private void UpdateValue() {
            Vector3 currentValue = _valueRP.Value;
            Vector3? parsedValue = TryParseValue();

            // パースが成功した場合のみReactivePropertyに反映
            if (parsedValue.HasValue) {
                _valueRP.Value = parsedValue.Value;
            } else {
                // ※パースに失敗した場合は元の値を再適用
                ApplyValue(currentValue);
            }
        }

        // Viewから値を読み取る.
        private Vector3? TryParseValue() {

            // parse values
            bool xParsed = float.TryParse(_xInputField.text, NumberStyles.Float, CultureInfo.InvariantCulture, out var x);
            bool yParsed = float.TryParse(_yInputField.text, NumberStyles.Float, CultureInfo.InvariantCulture, out var y);
            bool zParsed = float.TryParse(_zInputField.text, NumberStyles.Float, CultureInfo.InvariantCulture, out var z);

            // 全てのパースが成功した場合にのみ値を返す
            if (xParsed && yParsed && zParsed) {
                return new Vector3(x, y, z);
            }

            // いずれかのパースに失敗した場合はnullを返す
            return null;
        }

        // Viewに値を適用する.
        private void ApplyValue(Vector3 value) {
            _xInputField.text = value.x.ToFloatText(_decimalPlaces);
            _yInputField.text = value.y.ToFloatText(_decimalPlaces);
            _zInputField.text = value.z.ToFloatText(_decimalPlaces);
        }
    }
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?