現象
ボタンを押した際に 縮小->拡大のリアクションをつけた場合
押した場所が縮小した際にボタンから出てしまうと、下動画のようにピクピクしてしまう
上記の動作をするスクリプトは以下になる
public class ButtonTest : Button { // 縮小させる private void Press() // もとに戻す private void Release() public override void OnPointerDown(PointerEventData eventData) { Press(); base.OnPointerDown(eventData); } public override void OnPointerUp(PointerEventData eventData) { Release(); base.OnPointerUp(eventData); } public override void OnPointerEnter(PointerEventData eventData) { if (IsPressed()) Press(); base.OnPointerEnter(eventData); } public override void OnPointerExit(PointerEventData eventData) { if (IsPressed()) Release(); base.OnPointerExit(eventData); } }
原因
これが発生する原因としては、ボタンが縮小することにより、OnPointerExit
が呼ばれてしまい
サイズがもとに戻る、その際に OnPointerEnter
が呼ばれてを繰り返していることが原因になる
解決策
押したタイミングでボタンの Rect を取得し、OnPointerExit
時に Rect の範囲内かどうかの判定を行う
private Rect _pressRect; public override void OnPointerDown(PointerEventData eventData) { Press(); // ボタンの Rect を取得する _pressRect = GetRect(_rect, eventData); base.OnPointerDown(eventData); } public override void OnPointerExit(PointerEventData eventData) { // 押したタイミングの Rect範囲か確認 if (!_pressRect.Contains(eventData.position) && IsPressed()) Release(); base.OnPointerExit(eventData); }
しかしながら、まだ問題があり、OnPointerExit
は何度も呼ばれないため
押したあとに元あった範囲外に出たとしても Release が呼ばれずに縮小した状態になってしまいます
そのために、OnDrag
を追加し、押している最中の挙動を制御します
※Unity 2020.1.10f では OnDrag が呼ばれましたが旧Versionの場合呼ばれないことがありました
public void OnDrag(PointerEventData eventData) { if (!IsPressed()) return; if (_pressRect.Contains(eventData.position)) Press(); else Release(); }
これにより範囲外から戻った際にも縮小するように対応できた
最後にソースコードを貼っておくので、コードが見たい方はどうぞ
using DG.Tweening; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class ButtonTest : Button, IDragHandler { private Tweener _tweener; private RectTransform _rect; protected override void Awake() { base.Awake(); _rect = transform as RectTransform; onClick.AddListener(() => Debug.Log("Click")); } private void Tween(Vector3 to, string id) { if (_tweener != null && _tweener.stringId == id) return; _tweener?.Kill(); _tweener = _rect.DOScale(to, 0.1f) .SetId(id) ; } private void Press() { Tween(Vector3.one * 0.7f, "press"); } private void Release() { Tween(Vector3.one, "release"); } private Rect _pressRect; public override void OnPointerDown(PointerEventData eventData) { Press(); _pressRect = GetRect(_rect, eventData); base.OnPointerDown(eventData); } public override void OnPointerUp(PointerEventData eventData) { Release(); base.OnPointerUp(eventData); } public override void OnPointerEnter(PointerEventData eventData) { if (IsPressed()) Press(); base.OnPointerEnter(eventData); } public override void OnPointerExit(PointerEventData eventData) { if (!_pressRect.Contains(eventData.position) && IsPressed()) Release(); base.OnPointerExit(eventData); } public void OnDrag(PointerEventData eventData) { if (!IsPressed()) return; if (_pressRect.Contains(eventData.position)) Press(); else Release(); } private static readonly Vector3[] cache = new Vector3[4]; private static Rect GetRect(RectTransform rect, PointerEventData data) { rect.GetWorldCorners(cache); // Canvas が overlay の対応 if (data.enterEventCamera != null) { // Screen座標 に変換 (左下, 左上, 右下, 右上) for (var i = 0; i < cache.Length; i++) cache[i] = RectTransformUtility.WorldToScreenPoint(data.enterEventCamera, cache[i]); } return new Rect(cache[0].x, cache[0].y, cache[2].x - cache[0].x, cache[2].y - cache[0].y); } }