【Unity】UGUIのRectTransformを100倍快適に扱うためのユーティリティを作った

概要

この記事はCluster,Inc. Advent Calendar 2017の14日目の記事です。

f:id:notargs:20171214105828g:plain

こんな感じのWindowのようなものを作っていたのですが、 RectTransformの具体的なサイズが知りたい! 親Transformの左端、右端から、相対的に位置を指定したい! など、RectTransformに思うところが多かったので、RectTransformを便利に扱うためのユーティリティを作ってみました。

コード

/**
MIT Lisence

Copyright (c) 2017 Cluster,Inc.

This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
*/

public enum Corner
{
    LeftTop,
    RightTop,
    LeftBottom,
    RightBottom,
}
/**
MIT Lisence

Copyright (c) 2017 Cluster,Inc.

This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
*/

using System;
using System.Linq;
using UnityEngine;

public static class RectTransformUtil
{
    public static Bounds GetWorldBounds(this RectTransform rectTransform)
    {
        var corners = new Vector3[4];
        rectTransform.GetWorldCorners(corners);

        var center = corners.Aggregate(Vector3.zero, (current, corner) => current + corner) / corners.Length;
        var size = new Vector3(
            corners.Max(corner => corner.x) - corners.Min(corner => corner.x),
            corners.Max(corner => corner.y) - corners.Min(corner => corner.y),
            1);
        return new Bounds(center, size);
    }

    public static Vector2 GetRelativePosition(this RectTransform rectTransform, Corner corner)
    {
        var parentBounds = rectTransform.parent.GetComponent<RectTransform>().GetWorldBounds();
        var bounds = rectTransform.GetWorldBounds();

        var pos = Vector2.zero;
            
        switch (corner)
        {
            case Corner.LeftBottom:
            case Corner.RightBottom:
                pos.y = bounds.min.y - parentBounds.min.y;
                break;
            case Corner.LeftTop:
            case Corner.RightTop:
                pos.y = parentBounds.max.y - bounds.max.y;
                break;
            default:
                throw new ArgumentOutOfRangeException("corner", corner, null);
        }

        switch (corner)
        {
            case Corner.LeftTop:
            case Corner.LeftBottom:
                pos.x = bounds.min.x - parentBounds.min.x;
                break;
            case Corner.RightTop:
            case Corner.RightBottom:
                pos.x = parentBounds.max.x - bounds.max.x;
                break;
            default:
                throw new ArgumentOutOfRangeException("corner", corner, null);
        }

        return pos;
    }

    public static void SetRelativePosition(this RectTransform rectTransform, Vector2 pos, Corner corner)
    {
        var parentBounds = rectTransform.parent.GetComponent<RectTransform>().GetWorldBounds();
        var bounds = rectTransform.GetWorldBounds();
        var anchoredPosition = rectTransform.position;

        switch (corner)
        {
            case Corner.LeftBottom:
            case Corner.RightBottom:
                anchoredPosition.y += parentBounds.min.y - bounds.min.y + pos.y;
                break;
            case Corner.LeftTop:
            case Corner.RightTop:
                anchoredPosition.y += parentBounds.max.y - bounds.max.y - pos.y;
                break;
            default:
                throw new ArgumentOutOfRangeException("corner", corner, null);
        }
            
        switch (corner)
        {
            case Corner.LeftTop:
            case Corner.LeftBottom:
                anchoredPosition.x += parentBounds.min.x - bounds.min.x + pos.x;
                break;
            case Corner.RightTop:
            case Corner.RightBottom:
                anchoredPosition.x += parentBounds.max.x - bounds.max.x - pos.x;
                break;
            default:
                throw new ArgumentOutOfRangeException("corner", corner, null);
        }
            
        rectTransform.position = anchoredPosition;
    }
}

つかいかた

rectTransform.GetWorldBounds() のように、拡張メソッドとして使えるようになっています。

GetWorldBounds

Boundsとして、ワールド空間でのRectTransformの位置やサイズ、各点の位置などが取得できます。

GetRelativePosition / SetRelativePosition

右側/左側/上側/下側が、親の端からどのくらい離れているかを相対位置で取得/設定できます。 親のオブジェクトをはみ出さないように位置を制限する、といった挙動の実装に便利です。

微妙なところ

パフォーマンス

メソッドが呼ばれるたびにGetComponent<RectTransform>()を2回も呼んでいるので、それなりにパフォーマンスが悪いです。 適宜、必要に応じてキャッシュする仕組みなどを作ると良いと思います。

親がRectTransformじゃない場合の挙動は?

わからん 😱

親にスケールがかかっていた場合の挙動は?

わからん😇😇😇 なんかもっといいアプローチがあったら教えてください