📗

【Unity】UniRxで自作のカスタムボタンを作ろう

2024/10/02に公開

はじめに

初めての方も、そうでない方もこんにちは!
現役ゲームプログラマーのたむぼーです。
自己紹介を載せているので、気になる方は見ていただければ嬉しいです!

今回は
 UniRxでUnityのカスタムボタン
の作り方を紹介します

https://zenn.dev/tmb/articles/1072f8ea010299

補足

アニメーション管理はこちらを使ってます

合わせて使ってみてください~
https://zenn.dev/tmb/articles/4d4fb5a48e2982
https://zenn.dev/tmb/articles/8bc2b0506154d1

Unity標準ボタンではダメ?

UnityのUIにある、Unity標準ボタンでは機能不足になることが多く、
初めの設計を間違うと、あとあと大規模改修・・・コンポーネント張り替え地獄がまってます
そうならないためにも、初めの設計(特にボタン)は重要です

Unity標準ボタンは機能不足

Unity標準ボタンの機能は約3種類扱えない

Unity標準ボタンは拡張できない

標準ボタンをラップしてできるが、それでも標準ボタンとは違うコンポーネントになるので、拡張ができない(しない)と考えてもいい思います

Unity標準ボタンはボタン処理を一括修正ができない

標準ボタンは機能を拡張できないから、処理の一括修正もできません

Unity標準ボタンはInspectorから関数を設定できる

便利じゃん?と思うかもしれませんが、これの問題点・・・
それは、プログラム側で読み取れないこと

あれ、ボタンのクリック処理おかしい・・けど、処理はあってるな?状態になる

カスタムボタンとは・・・

MonoBehaviourクラスと、各イベントのIPointer〇〇と付くインターフェースを継承して実装します。

public class ButtonEx : MonoBehaviour,
    IPointerClickHandler,
    IPointerDownHandler,
    IPointerUpHandler
{
}

実際の処理

こちらは、クリック時、ボタンが押された時、ボタンが離された時の状態を扱えるようになってます。
必要に応じて拡張、改変してください

カスタムボタン
ButtonEx.cs
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UniRx;

namespace Utils
{
    public class ButtonEx : MonoBehaviour,
        IPointerClickHandler,
        IPointerDownHandler,
        IPointerUpHandler
    {
        [Header("ボタン設定")]

        [SerializeField]
        private bool _isInteractable = true;

        [SerializeField]
        private Color32 _disabledColor = new Color32(255, 255, 255, 128);

        [Header("アニメーション設定")]

        [SerializeField]
        private string _pointerDownStateName = "PointerDown";

        [SerializeField]
        private string _pointerUpStateName = "PointerUp";

        private Image _image;
        private Animator _animator;
        private Color32 _defaultColor;
        private bool _isClickSubscribers = false;
        private bool _isPointerDownSubscribers = false;
        private bool _isPointerUpSubscribers = false;

        // Subject関連
        private readonly Subject<Unit> _onClickSubject = new Subject<Unit>();
        private readonly Subject<Unit> _onPointerDownSubject = new Subject<Unit>();
        private readonly Subject<Unit> _onPointerUpSubject = new Subject<Unit>();

        /// <summary> デバウンスのミリ秒 </summary>
        private const int dueMilliseconds = 500;

        /// <summary>
        /// 開始時
        /// </summary>
        private void Start()
        {
            _image = GetComponent<Image>();
            _animator = GetComponent<Animator>();

            _defaultColor = _image.color;
        }

        /// <summary>
        /// 破壊時
        /// </summary>
        private void OnDestroy()
        {
            _onClickSubject?.Dispose();
            _onPointerDownSubject?.Dispose();
            _onPointerUpSubject?.Dispose();
        }

        /// <summary>
        /// クリックしたときイベント
        /// </summary>
        public IObservable<Unit> OnClickAsObservable()
        {
            _isClickSubscribers = true;
            return _onClickSubject.AsObservable().ThrottleFirst(TimeSpan.FromMilliseconds(dueMilliseconds));
        }

        /// <summary>
        /// 押されたときのイベント
        /// </summary>
        public IObservable<Unit> OnPointerDownAsObservable()
        {
            _isPointerDownSubscribers = true;
            return _onPointerDownSubject.AsObservable();
        }

        /// <summary>
        /// 離されたときのイベント
        /// </summary>
        public IObservable<Unit> OnPointerUpAsObservable()
        {
            _isPointerUpSubscribers = true;
            return _onPointerUpSubject.AsObservable();
        }

        /// <summary>
        /// 有効状態の設定
        /// </summary>
        public void SetInteractable(bool isInteractable)
        {
            _isInteractable = isInteractable;
            SetColor(_isInteractable ? _defaultColor : _disabledColor);
        }

        /// <summary>
        /// 押されたときのアニメーションを変更
        /// </summary>
        public void SetPointerDownStateName(string stateName)
        {
            _pointerDownStateName = stateName;
        }

        /// <summary>
        /// 離されたときのアニメーションを変更
        /// </summary>
        public void SetPointerUpStateName(string stateName)
        {
            _pointerUpStateName = stateName;
        }

        /// <summary>
        /// クリック
        /// </summary>
        public void OnPointerClick(PointerEventData eventData)
        {
            if (_isInteractable)
            {
                if (_isClickSubscribers)
                {
                    _onClickSubject.OnNext(Unit.Default);
                }
            }
        }

        /// <summary>
        /// 押した
        /// </summary>
        public void OnPointerDown(PointerEventData eventData)
        {
            if (_isInteractable)
            {
                if (_isPointerDownSubscribers)
                {
                    _onPointerDownSubject.OnNext(Unit.Default);
                }
                AnimationManager.Instance.AddAnimationObject(gameObject, _pointerDownStateName, _animator);
            }
        }

        /// <summary>
        /// 離した
        /// </summary>
        public void OnPointerUp(PointerEventData eventData)
        {
            if (_isInteractable)
            {
                if (_isPointerUpSubscribers)
                {
                    _onPointerUpSubject.OnNext(Unit.Default);
                }
                AnimationManager.Instance.AddAnimationObject(gameObject, _pointerUpStateName, _animator);
            }
        }

        /// <summary>
        /// 色変更
        /// </summary>
        private void SetColor(Color32 color)
        {
            if (_image != null)
            {
                _image.color = color;
            }
        }
    }
}
使用例
using UniRx;
using UnityEngine;
using Utils;

public class Example : MonoBehaviour
{
    [SerializeField]
    private ButtonEx _button;

    private void Start()
    {
        // ボタンクリック時の処理
        _button.OnClickAsObservable().Subscribe(_ => OnClick()).AddTo(this);
    }

    private void OnClick()
    {
        Debug.Log("ボタンクリック時に実行したい処理");
    }
}

Discussion