🔆

UniRxでボタンの長押しとかダブルクリックとかポインタのIn/Outとか

2024/02/27に公開

UniRxでボタンの長押しとかダブルクリックとかポインタのIn/Outとか

長押し、ダブルクリック、ポインタのIn/Out。
ユーザがインタラクションするUI部品として、ボタンにはさまざまなイベントコールバックが求められますが、Unity標準のボタンにはそれらのイベントがありません。
するしかねぇ、自作……! ということで、わたしが自作したものと、そのユースケースに応じた使い方をここに書きます。

https://gist.github.com/nekomimi-daimao/7b29fbb6f5bda1bf71791412f35fa7ea

ユースケース

長押し

var longPressSecond = 1f;
_buttonExtended.OnClick
    .Where(f => f > longPressSecond)
    .Subscribe((_ => { Debug.Log("long pressed!"); }));

長押しで経過時間を取得

_buttonExtended.PressedSecond
    // 購読開始時に通知が来てしまうので初回は無視
    .Skip(1)
    .Subscribe(f =>
    {
        // 例えばボタンを押している秒数に応じてチャージ段階が変わる
        var chargePhase3 = 3f;
        var chargePhase2 = 2f;
        var chargePhase1 = 0.1f;
        if (f > chargePhase3)
        {
            Debug.Log("chargePhase 3");
        }
        else if (f > chargePhase2)
        {
            Debug.Log("chargePhase 2");
        }
        else if (f > chargePhase1)
        {
            Debug.Log("chargePhase 1");
        }
        else
        {
            Debug.Log("chargePhase 0");
        }
    });

ダブルクリック

// ダブルクリックと見做すインターバル秒
var doubleClickIntervalSecond = 0.3f;
_buttonExtended.OnClick
    .Select(_ => Time.realtimeSinceStartup)
    .Pairwise()
    .Select(pair => pair.Current - pair.Previous)
    .Where(f => f < doubleClickIntervalSecond)
    .Subscribe(f => { Debug.Log("double click"); });

ポインタのIN/OUT

_buttonExtended.PointerIn
    .Subscribe(b => { Debug.Log($"pointer {(b ? "in" : "out")}"); });

拡張

さまざまなタイミングで音を鳴らしたり画像を変えたり、プロジェクトによっていろんな要件があると思います。もっと言えば同じプロジェクト内でもいろんなボタンが必要になったりもします。
そのたびにコピペして拡張するのは虚無なので、このクラスを継承して拡張できるよう、Start()から呼ばれるOnStart()というvirtualなメソッドを定義しておきました。ButtonExtendedを継承してpublic override void OnStart()すれば動作を自由にできます。
別に継承先のクラスでStart()とか書けばよくない? と思うかもしれませんが、MonoBehaviourで継承元と継承先でStart()があったらどっちが呼ばれるんだろう……?[1] と考えることになった時点で「負け」なので定義しています。

実用

こんな感じで継承して拡張します。

using Nekomimi.Daimao;
using TMPro;
using UniRx;
using UnityEngine;
using UnityEngine.UI;

public class ButtonCharge : ButtonExtended
{
    [SerializeField]
    private Image imageSelected;

    [SerializeField]
    private Image imageProgress;

    [SerializeField]
    private TMP_Text textProgress;

    private const float MaxTime = 4f;

    public override void OnStart()
    {
        PointerIn
            .TakeUntilDestroy(this)
            .Subscribe(b => imageSelected.enabled = b);

        PressedSecond
            .TakeUntilDestroy(this)
            .Subscribe(f =>
            {
                imageProgress.fillAmount = Mathf.Min(f / MaxTime, 1f);
                textProgress.text = Mathf.Min(f, MaxTime).ToString("F1");
            });
    }
}

こんな感じで動きます。

まとめ

これで求められうるものはカバーできているはずです。
他は仕様バグ!!!!!そんな難しい操作!!!いらない!!!!!!

おしまい。

補遺

CanvasGroupもつけるようにしてあるので、有効/無効や透明度はそこから操作してください。

以前書いた記事だと長押ししながらポインターがボタン外に出た場合をカバーしてないのよくないな! ということで再作成しました。

UniRxは最近Public archiveしてR3使ってね![2] ということになりましたがnamespaceと一部のクラスを書き換えればそのまま動くはずです試してないですが。

参考

https://shibuya24.info/entry/unity-ui-ban-default-button#方法②フルスクラッチ(おすすめ)

https://yotiky.hatenablog.com/entry/unity_timerprogressring

https://siuncyclone.hatenablog.com/entry/2018/10/11/124547

脚注
  1. 継承先が呼ばれるっぽいです ↩︎

  2. SubscribeAwait × AwaitOperation.Drop、いい。 ↩︎

Discussion