uGUIのScrollRect上にButtonを表示しているケースでButtonの反応が著しくシビアだったりする。
この件は以前から世界中で皆が悲鳴を上げている症状。
Unity本体での対応も予定されているようだが実装時期は未定の模様。情報を検索しているとだいたい定番と言える対応が見えてくる。
今回は、こちらのページUnityのuGUIでスクロール中にボタンの長押しをするスクリプトを参考に自分なりの対応を行ってみた。
自分のケースでは長押しは不要で取りこぼしているであろうButtonのクリックを再現できればよいだけなので内容はシンプルになる。
ちょっとだけ工夫したのはクリックと見なした場合のアプリケーション側への通知をUnityEventで行うこと。
ScrollRectを継承して、ドラッグの開始と終了で移動距離をチェックし閾値以下であればアプリケーション側へ通知する処理を追加。
この内容だと厳密にはクリック動作と言えない判定ではあるけれど実用上はあまり問題にならないと思う。
スクリプト
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class ScrollRectEx : ScrollRect
{
public ScrollRectExEvent onClicked;
const float MAX_DISTANCE = 20.0F;
private Vector2 startPos;
new void Start()
{
base.Start();
if (onClicked == null) {
onClicked = new ScrollRectExEvent();
}
}
public override void OnBeginDrag(PointerEventData eventData)
{
startPos = eventData.position;
base.OnBeginDrag(eventData);
}
public override void OnEndDrag(PointerEventData eventData)
{
base.OnEndDrag(eventData);
float distance = Vector2.Distance(startPos, eventData.position);
if (distance < MAX_DISTANCE) {
onClicked.Invoke(eventData);
}
}
}
アプリケーション側へ通知する際のイベントを用意。
using UnityEngine.Events;
using UnityEngine.EventSystems;
public class ScrollRectExEvent : UnityEvent<PointerEventData>
{
}
(上の例ではファイルを分けているが用途を考えると一つのファイルにまとめる方が妥当かもしれない)
使い方
アイテムのリストを表示するスクロールビューのオブジェクト ItemSelector(仮名) がある場合
まずは ItemSelector(仮名) にコンポーネントとして 上記の ScrollRectEx を追加する。
そして ItemSelector(仮名) を管理するスクリプト ABC.cs がある場合、次のように処理を用意する。
void Start()
{
var component = GetComponent<ScrollRectEx>();
component.onClicked.AddListener(OnClicked);
// その他アプリケーション固有の処理
}
public void OnClicked(PointerEventData eventData)
{
// このメソッド内はアプリケーション毎に対応が変わる。以下はあくまでも参考。
var component = eventData.pointerPressRaycast.gameObject.GetComponent<Content>();
if (component != null) {
// クリックされたであろうUIパーツにコンポーネントXYZが存在する場合は通知対象とみなす
// そしてそのパーツが通常クリックされたときと同じコールバックを強制的に呼び出す
component.OnButtonPressed();
}
}
これで実機での操作ストレスが軽減できると思う。