UniRxでボタンの長押しとかダブルクリックとかポインタのIn/Outとか
UniRxでボタンの長押しとかダブルクリックとかポインタのIn/Outとか
長押し、ダブルクリック、ポインタのIn/Out。
ユーザがインタラクションするUI部品として、ボタンにはさまざまなイベントコールバックが求められますが、Unity標準のボタンにはそれらのイベントがありません。
するしかねぇ、自作……! ということで、わたしが自作したものと、そのユースケースに応じた使い方をここに書きます。
ユースケース
長押し
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
と一部のクラスを書き換えればそのまま動くはずです試してないですが。
参考
Discussion