RectTransformのRectに沿ったラインを引く

UGUIを組んでいると、RectTransformに対して線を引きたい場合が有ります。
例えばガイドラインだったり、例えばカーソルだったり。
そんな時に利用できるRectLineコンポーネントを作りました。
![スクリーンショット 2021-10-11 173113.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61266/f907b492-038d-f8ed-ecb4-fd4af3a4be5e.png)

```cs:RectLine.cs
using System;
using UnityEngine;
using UnityEngine.UI;

namespace ScreenPocket.UI
{
    /// <summary>
    /// RectLineのブレンドモード
    /// </summary>
    public enum RectLineBlendMode
    {
        /// <summary>
        /// 乗算
        /// </summary>
        Multiply,
        /// <summary>
        /// 加算
        /// </summary>
        Additive,
        /// <summary>
        /// 減算
        /// </summary>
        Subtract,
    }

    /// <summary>
    /// ライン位置
    /// 内側か、中間か、外側か
    /// </summary>
    public enum RectLinePositionType
    {
        /// <summary>
        /// 矩形の内側
        /// </summary>
        Inside,
        /// <summary>
        /// 矩形の中間
        /// </summary>
        Middle,
        /// <summary>
        /// 矩形の外側
        /// </summary>
        Outside
    }
    
    /// <summary>
    /// Rectに合わせた線を引く
    /// </summary>
    [RequireComponent(typeof(RectTransform))]
    [RequireComponent(typeof(CanvasRenderer))]
    public sealed class RectLine : MaskableGraphic
    {
        /// <summary>
        /// テクスチャ（一応）
        /// </summary>
        [SerializeField]
        private Texture2D _texture;

        /// <summary>
        /// アウトラインの太さ
        /// </summary>
        [SerializeField]
        private float _size;
        
        [SerializeField]
        private Color _inColor = Color.white;
        [SerializeField]
        private Color _outColor = Color.white;

        [SerializeField]
        private RectLineBlendMode _blendMode = RectLineBlendMode.Multiply;

        [SerializeField]
        private RectLinePositionType _positionType = RectLinePositionType.Middle;
        
        public override Texture mainTexture => _texture != null ? _texture : Texture2D.whiteTexture;

        protected override void OnPopulateMesh(VertexHelper vh)
        {
            var r = GetPixelAdjustedRect();
            var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height);

            var inLength = _positionType switch
            {
                RectLinePositionType.Inside => _size * Mathf.Sqrt(2f),
                RectLinePositionType.Middle => _size * Mathf.Sqrt(2f) * 0.5f,
                RectLinePositionType.Outside => 0f,
                _ => throw new ArgumentOutOfRangeException()
            };
            var outLength = _positionType switch
            {
                RectLinePositionType.Inside => 0f,
                RectLinePositionType.Middle => _size * Mathf.Sqrt(2f) * 0.5f,
                RectLinePositionType.Outside => _size * Mathf.Sqrt(2f),
                _ => throw new ArgumentOutOfRangeException()
            };

            //ローカル関数
            //合成した色を返す
            Color32 GetColor(Color target)
            {
                var ret = _blendMode switch
                {
                    RectLineBlendMode.Multiply => target * color,
                    RectLineBlendMode.Additive => target + color,
                    RectLineBlendMode.Subtract => target - color,
                    _ => throw new ArgumentOutOfRangeException()
                };
                
                //アルファはcolorの物を使う（扱いやすいので）
                ret.a = color.a;
                return ret;
            }

            var inColor = GetColor(_inColor);
            var outColor = GetColor(_outColor);
            
            vh.Clear();
            var lb = new Vector3(v.x, v.y);
            var lt = new Vector3(v.x, v.w);
            var rt = new Vector3(v.z, v.w);
            var rb = new Vector3(v.z, v.y);

            var lbIn = lb + new Vector3(inLength, inLength);
            var lbOut = lb + new Vector3(-outLength, -outLength);
            vh.AddVert(lbIn, inColor, new Vector2(0f, 0f));
            vh.AddVert(lbOut, outColor, new Vector2(0f, 1f));
            vh.AddVert(lt + new Vector3(inLength,-inLength), inColor, new Vector2(1f, 0f));
            vh.AddVert(lt + new Vector3(-outLength,outLength), outColor, new Vector2(1f, 1f));
            vh.AddVert(rt + new Vector3(-inLength,-inLength), inColor, new Vector2(2f, 0f));
            vh.AddVert(rt + new Vector3(outLength,outLength), outColor, new Vector2(2f, 1f));
            vh.AddVert(rb + new Vector3(-inLength,inLength), inColor, new Vector2(3f, 0f));
            vh.AddVert(rb + new Vector3(outLength,-outLength), outColor, new Vector2(3f, 1f));
            vh.AddVert(lbIn, inColor, new Vector2(4f, 0f));
            vh.AddVert(lbOut, outColor, new Vector2(4f, 1f));

            vh.AddTriangle(0, 1, 2);
            vh.AddTriangle(2, 1, 3);
            vh.AddTriangle(2, 3, 4);
            vh.AddTriangle(4, 3, 5);
            vh.AddTriangle(4, 5, 6);
            vh.AddTriangle(6, 5, 7);
            vh.AddTriangle(6, 7, 8);
            vh.AddTriangle(8, 7, 9);
        }
    }
}

```
* 太さの調整
* テクスチャを指定可能(内側がv0、外側がv1各辺がu0～1、u1～2、u2～3、u3～4とタイリング)
* 矩形の内側、中間、外側で位置を調整可能
* 内周色、外周色を指定可能
* 内周/外周色とGraphicのColorのブレンド方法を選択可能

となっているのでソコソコ多機能かな？と。
MaskableGraphic派生なので、当然マスク対象にもできるし、Maskコンポーネントと組み合わせれば、ライン部分のみマスクをかける事も可能です。

次作るゲームのカーソルはコレで作ろうかな。