うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】RectTransform の範囲内で重ならないようにランダムで配置する

親の RectTransform 内に重複を考慮せずに、ランダムに配置した場合は 下図のように重複してしまう場合がある

f:id:hacchi_man:20211017172953p:plain

そのため、重複しないようにランダムに配置するスクリプトを作成した

f:id:hacchi_man:20211017173250p:plain

これにより、上図のように配置することができる

もちろん、配置する数が多すぎると空きスペースが見つからなく

無限ループしてしまうので、その点は注意が必要になる

Canvas が Overlay の場合のみ利用できる

使い方

[SerializeField]
private RectTransform _root;

private List<RectTransform> _list;

private void Random()
{
    // ランダムにRectを配置する
    RectTransformUtility.SetRandomPosition(_root, _list, 50);
}

スクリプト

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

public class RectTransformUtility
{ 
    /// <summary>
    /// RectTransform を Root の Rect 内部でランダムに配置する
    /// </summary>
    public static void SetRandomPosition(RectTransform root, List<RectTransform> transforms, float offset)
    {
        var rootRect = new RectPosition(root, offset);
        var cacheRects = new List<RectPosition>();
        foreach (var obj in transforms)
        {
            var rectTransform = obj.transform as RectTransform;
            bool isNotContains;
            RectPosition rectPosition;
            Vector2 pos;
            do
            {
                isNotContains = true;
                pos = rootRect.GetRandomPosition(new Vector2(rectTransform.rect.width, rectTransform.rect.height));
                rectTransform.position = pos;
                rectPosition = new RectPosition(rectTransform, offset);

                foreach (var cacheRect in cacheRects)
                {
                    if (cacheRect.Contains(rectPosition))
                    {
                        isNotContains = false;
                        break;
                    }
                }

            } while (!isNotContains);

            cacheRects.Add(rectPosition);
        }
    }

    private class RectPosition
    {
        private readonly Vector3[] _corners = new Vector3[4];
        private readonly float _offset;
        private Vector2 _max;
        private Vector2 _min;

        public float xMin => _min[0];
        public float xMax => _max[0];
        public float yMin => _min[1];
        public float yMax => _max[1];

        public RectPosition(RectTransform rectTransform, float offset)
        {
            rectTransform.GetWorldCorners(_corners);
            _offset = offset;
            _max = new Vector2(_corners.Max(c => c.x), _corners.Max(c => c.y));
            _min = new Vector2(_corners.Min(c => c.x), _corners.Min(c => c.y));
        }

        /// <summary>
        /// ランダムな座標を取得
        /// </summary>
        /// <param name="elementSize">Rect のサイズを渡す内部 Pivot は Center想定で半分にする</param>
        public Vector2 GetRandomPosition(Vector2 elementSize)
        {
            var hWidth = elementSize.x / 2f;
            var hHeight = elementSize.y / 2f;

            var x = Random.Range(xMin + hWidth, xMax - hWidth);
            var y = Random.Range(yMin + hHeight, yMax - hHeight);
            return new Vector2(x, y);
        }

        public bool Contains(RectPosition rect)
        {
            if (xMin - _offset <= rect.xMin && rect.xMin <= xMax + _offset ||
                xMin - _offset <= rect.xMax && rect.xMax <= xMax + _offset)
            {
                if (yMin - _offset <= rect.yMin && rect.yMin <= yMax + _offset ||
                    yMin - _offset <= rect.yMax && rect.yMax <= yMax + _offset)
                {
                    return true;
                }
            }
            return false;
        }
    }
}