破線の矩形を描いてみた

# 前回までのお話

以前の記事で破線を描くShaderについて記載しました。
[固定間隔の破線を描くシェーダーを作ってみた](https://qiita.com/f_tsucchy/items/a3e4773ac031600372e4)

今回は破線で矩形を描いてみます。
目標は↓みたいな破線の長さや間隔が変わらない矩形です。

![固定長破線.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969992/79aec85c-3893-47aa-48f1-9ae9d12e8cf7.gif)

使用するShaderは前回紹介した破線の長さと間隔を指定できるものを使用します。

## 矩形を描画してみる
UILineRendererを用いて矩形を描くコードを描いてみました。
UILineRendererには座標を指定して繋ぐ機能があるので、4点＋スタートの1点を指定することで簡単に矩形を描くことができます。

### 水平方向基準
以前の記事で紹介したShaderで線の長さの基準の「_Length」を指定する必要があったので、下記のコードのように_Lengthを水平方向の長さを指定してみました。
他の設定値もコード側から設定しています。

```C#
[RequireComponent( typeof( RectTransform ) )]
public class RectLineRenderer : MonoBehaviour
{
    [Header( "矩形の線の描画に使用するPrefab" )]
    [SerializeField]
    private UILineRenderer lineRendererPrefab = null;

    [Header( "線の描画に使用するMaterial" )]
    [SerializeField]
    private Material material = null;

    [Header( "線の太さ" )]
    [SerializeField]
    [Range( 1, 500 )]
    private int lineThickness = 5;

    [Header( "破線の描画する線の長さ" )]
    [SerializeField]
    [Range( 1, 1000 )]
    private int drawLineLength = 10;

    [Header( "破線の描画しない部分の長さ" )]
    [SerializeField]
    [Range( 1, 1000 )]
    private int intervalLength = 10;

    [Header( "破線の色" )]
    [SerializeField]
    private Color32 color32 = new Color32( 255, 255, 255, 255 );

    [Header( "破線の描画に使用するsprite" )]
    [SerializeField]
    private Sprite sprite = null;

    private RectTransform rectTransform = null;
    private RectTransform RectTransform
    {
        get
        {
            if( rectTransform == null )
            {
                rectTransform = GetComponent<RectTransform>();
            }

            return rectTransform;
        }
    }

    private UILineRenderer lineRenderer = null;

    private void Awake()
    {
        InitailzeLineRenderer();
    }

    private void InitailzeLineRenderer()
    {
        if( material == null )
        {
            return;
        }

        if( lineRenderer != null )
        {
            return;
        }

        // 子オブジェクトとしてUILineRendererを追加する
        lineRenderer = Instantiate( lineRendererPrefab, transform );

        // 値を設定する
        lineRenderer.sprite = sprite;
        lineRenderer.lineThickness = lineThickness;


        // リスト化をoff
        // これをONにすると2点で1本の線という扱いになり、
        // 複数の線を繋げずに引くことができる
        // これをOFFにすると Points に設定した点を順にすべて繋ぐようになる      
        lineRenderer.lineList = false;

        // Materialの初期情報を書き込む
        lineRenderer.material = new Material( material );
        lineRenderer.material.SetColor( Shader.PropertyToID( "_Color" ), color32 );
        lineRenderer.material.SetFloat( Shader.PropertyToID( "_Draw" ), drawLineLength );
        lineRenderer.material.SetFloat( Shader.PropertyToID( "_Interval" ), intervalLength );
    }

    /// <summary>
    /// 線の描画実施
    /// </summary>
    public void DrawLine()
    {
        InitailzeLineRenderer();

        if( lineRenderer.material == null )
        {
            return;
        }

        // 矩形の角4点＋最初の角の点の合計5点を設定すれば良い
        List<Vector2> cornerPoints = new List<Vector2>();

        // 矩形の中心基準の位置
        cornerPoints.Add( new Vector2( - RectTransform.sizeDelta.x / 2.0f - lineThickness / 2.0f, RectTransform.sizeDelta.y / 2.0f + lineThickness / 2.0f ) );

        // 左右対称の値のはずなので計算した値を使う
        cornerPoints.Add( new Vector2( -cornerPoints[ 0 ].x, cornerPoints[ 0 ].y ) );

        cornerPoints.Add( new Vector2( cornerPoints[ 1 ].x, -cornerPoints[ 0 ].y ) );

        cornerPoints.Add( new Vector2( cornerPoints[ 0 ].x, cornerPoints[ 2 ].y ) );

        cornerPoints.Add( new Vector2( cornerPoints[ 0 ].x, cornerPoints[ 0 ].y ) );

        // 水平方向基準
        lineRenderer.material.SetFloat( Shader.PropertyToID( "_Length" ), Vector2.Distance( cornerPoints[ 0 ], cornerPoints[ 1 ] ) );
        lineRenderer.Points = cornerPoints.ToArray();
    }
}
```

色はこんな感じにしておきました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969992/5608bd13-df9c-5c6f-9f1d-5295aad6980a.png)


実際に動作させるとこんな感じです↓

![矩形(水平方向基準) 1.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969992/74d8bb3a-0aa7-ee88-8c6b-9c94fdfab42a.gif)

_Lengthが水平方向固定のため、水平方向の描画は線の長さが一定ですが、垂直方向は長さが一定になりません。
また、水平方向でも下部の線は横に広げた時に上の線と逆方向に線が追加されているように見えます。

これは線の描画の方向が上辺では左→右と描画しているのが、下辺では右→左となっているためです。
同様に垂直方向も右辺は上→下に描画、左辺は下→上に描画しているためです。

### 垂直方向基準

水平方向基準にしていた設定を垂直方向基準に書き換えました。

```C#
// 垂直方向基準
lineRenderer.material.SetFloat( Shader.PropertyToID( "_Length" ), Vector2.Distance( cornerPoints[ 0 ], cornerPoints[ 3 ] ) );
lineRenderer.Points = cornerPoints.ToArray();
```

垂直方向の破線の長さや間隔は一定になりましたが、今度は水平方向が一定ではなくなりました。

![矩形(垂直方向基準) (1).GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969992/007224b9-f1bc-be6c-443d-1e64d8587bfa.gif)


### 対角線基準
対角線を基準に変更してみました。
```C#
// 対角線基準
lineRenderer.material.SetFloat( Shader.PropertyToID( "_Length" ), Vector2.Distance( cornerPoints[ 0 ], cornerPoints[ 2 ] ) );
lineRenderer.Points = cornerPoints.ToArray();
```

![矩形(対角線基準).GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969992/eafaadce-b5f3-9b4c-84fd-9c2e78461304.gif)

水平方向垂直方向共に破線の長さと間隔が一定ではなくなりました。

## 水平方向と垂直方向の描画を分ける

元のコードの一部を変更し、各方向毎に設定を持てるようにしてみました。
UILineRendererのlineListの設定をtrueにする＆座標の設定順を整理することで水平方向は左→右で描画、垂直方向は上→下で描画できるようにしています。
lineListの設定をtrueにしてるので、今回は4点設定すればOKです。

```C#
/// <summary>
/// 各方向の設定
/// </summary>
private enum LineDirection : int
{
    Horizontal,
    Vertical,
}

private Dictionary<LineDirection, UILineRenderer> lineRenderers = new Dictionary<LineDirection, UILineRenderer>();

private void InitailzeLineRenderer()
{
    if( lineRenderers.Count != 0 )
    {
        return;
    }

    if( material == null )
    {
        return;
    }

    foreach( LineDirection lineDirection in Enum.GetValues( typeof( LineDirection ) ) )
    {
        // 子オブジェクトとしてUILineRendererを追加する
        lineRenderers.Add( lineDirection, Instantiate( lineRendererPrefab, transform ) );

        // 値を設定する
        lineRenderers[ lineDirection ].sprite = sprite;
        lineRenderers[ lineDirection ].lineThickness = lineThickness;

        // リスト化をON
        // これをONにすると2点で1本の線という扱いになり、
        // 複数の線を繋げずに引くことができる
        // これをOFFにすると Points に設定した点を順にすべて繋ぐようになる
        lineRenderers[ lineDirection ].lineList = true;

        // Materialの初期情報を書き込む
        lineRenderers[ lineDirection ].material = new Material( material );
        lineRenderers[ lineDirection ].material.SetColor( Shader.PropertyToID( "_Color" ), color32 );
        lineRenderers[ lineDirection ].material.SetFloat( Shader.PropertyToID( "_Draw" ), drawLineLength );
        lineRenderers[ lineDirection ].material.SetFloat( Shader.PropertyToID( "_Interval" ), intervalLength );
    }
}

/// <summary>
/// 線の描画
/// </summary>
public void DrawLine()
{
    InitailzeLineRenderer();

    if( lineRendererPrefab.material == null )
    {
        return;
    }

    // 矩形の角4点
    List<Vector2> cornerPoints = new List<Vector2>();
    cornerPoints.Add( new Vector2( - RectTransform.sizeDelta.x / 2.0f - lineThickness / 2.0f, RectTransform.sizeDelta.y / 2.0f + lineThickness / 2.0f ) );

    cornerPoints.Add( new Vector2( -cornerPoints[ 0 ].x, cornerPoints[ 0 ].y ) );

    cornerPoints.Add( new Vector2( cornerPoints[ 0 ].x, -cornerPoints[ 0 ].y ) );

    cornerPoints.Add( new Vector2( cornerPoints[ 1 ].x, cornerPoints[ 2 ].y ) );

    // 水平方向の設定
    SetLineRendererValueForChangeSize( LineDirection.Horizontal, cornerPoints );

    // 垂直方向の設定
    // 水平方向の角の位置を調整して突っ込む
    SetLineRendererValueForChangeSize( LineDirection.Vertical, new List<Vector2>()
    {
        cornerPoints[ 0 ],
        cornerPoints[ 2 ],
        cornerPoints[ 1 ],
        cornerPoints[ 3 ],
    });

}

/// <summary>
/// 方向毎に長さと座標を設定
/// </summary>
/// <param name="lineDirection"></param>
/// <param name="points"></param>
private void SetLineRendererValueForChangeSize( LineDirection lineDirection, List<Vector2> points )
{
    lineRenderers[ lineDirection ].material.SetFloat( Shader.PropertyToID( "_Length" ), Vector2.Distance( points[ 0 ], points[ 1 ] ) );
    lineRenderers[ lineDirection ].Points = points.ToArray();
}
```

![矩形(水平垂直別々).GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969992/e7b4ecc5-3dc2-b057-5b7b-d06b5f1f8d80.gif)

水平・垂直どちらの方向も破線の長さと間隔が一定になりました。