ダリアメモ@ブログ

日々思ったことや自分用のメモも含めプログラムの技術を淡々と書いていきます

RectTransformを使ってButtonの当たり判定を複数持ちたかった話

概要

uGuiのButtonを特定の範囲だけ選択したら押せるようにしたかったため調べたことをまとめて記事にしようと思いました。

f:id:daria_sieben:20190805000606p:plain

調べてみた所、大きさを変えたり範囲外にしたりするにはICanvasRaycastFilterを用いるのが良さそうなのがわかりました。

tsubakit1.hateblo.jp

今回のやりたいことを実現するために考えられる方法として

1.Rectを使う
→GameObjectが一つで済むので処理負荷があまりなさそう
→複数解像度対応などCanvasScalerでやっている処理を自前で書かないといけない
→シーン上に表示されないのでエディタ拡張を用いてHandleなどシーン上で調整しやすいような環境を整えると作業が楽になる
Handleを用いたRectクラスをシーン上で視覚的に確認しやすくなっている記事を参考にしました。 light11.hatenadiary.com

2.RectTransformを使う
→GameObjectを必要な数分用意しないといけないので、処理負荷が1に比べて高そう
→複数解像度対応をCanvasScalerでやってくれるので気にしなくていい
→RectTransformで範囲を設定できるのでエディタ拡張をしなくて済む

今回は簡単に実装がしたかったため、2の方法で実装してみようと思います

実装

やることは、UIのワールド座標=スクリーン座標に変換して、変換した位置にタッチ座標が重なっていれば選択できるようにすることです。

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class RaycastRectangleFilter : MonoBehaviour, ICanvasRaycastFilter
{
  //子オブジェクトからも設定したいと考えた場合のためにSerializedFieldで設定する
    [SerializeField] private List<RectTransform> _rectTransforms = new List<RectTransform>();    
    public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
    {
    //各RectTransformのタッチ判定が一つでも成功したら許可する
        return _rectTransforms.Any(rectTransform =>
        {
       //スクリーン座標が欲しいのでUI座標からワールド座標に変換する
            var worldCorners = new Vector3[4];
            rectTransform.GetWorldCorners(worldCorners);
            var result = new Rect(
                worldCorners[0].x,
                worldCorners[0].y,
                worldCorners[2].x - worldCorners[0].x,
                worldCorners[2].y - worldCorners[0].y);
       //変換したスクリーン座標にタッチ座標が重なっていれば押せることを許可
            return result.Contains(sp);
        });
    }
}

参考

stackoverflow.com