うにてぃブログ

UnityやUnreal Engineの記事を書いていきます

【Unity】角丸な Image を使う

Image を角丸にする処理を探していると material を利用しているものが多く
material の差し替えが面倒だったので BaseMeshEffect を利用してみました

Meshの頂点が結構増えますが、Text の Outline や Shadow よりはましなはず ※左下のやつで154頂点

色を指定したり、画像を指定することもできます

f:id:hacchi_man:20200423023940p:plain:w400

以前紹介した GradientImageFilled を利用すればグラデーションのゲージとかもできたりします

https://hacchi-man.hatenablog.com/entry/2020/04/29/220000hacchi-man.hatenablog.com

f:id:hacchi_man:20200423025938p:plain

コード

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Image))]
public class RoundedCornersImage : BaseMeshEffect
{
    [SerializeField]
    private float _length = 0f;
    [SerializeField, Range(3, 50)]
    private int _division = 10;
    
    private List<UIVertex> _vertexList = new List<UIVertex>();
    private Vector2 _min;
    private Vector2 _max;
    private float _offset;
    private Color _color;
    
    public override void ModifyMesh(VertexHelper helper)
    {
        _color = (graphic as Image).color;
        _vertexList.Clear();
        helper.GetUIVertexStream(_vertexList);
        
        _min = new Vector2(_vertexList[0].position.x, _vertexList[0].position.y); 
        _max = new Vector2(_vertexList[0].position.x, _vertexList[0].position.y);
        foreach (var vertex in _vertexList)
        {
            for (var i = 0; i < 2; i++)
            {
                if (_min[i] > vertex.position[i])
                    _min[i] = vertex.position[i];
                if (_max[i] < vertex.position[i])
                    _max[i] = vertex.position[i];
            }
        }
        
        _offset = Mathf.Clamp(_length, 0f, Mathf.Min((_max[0] - _min[0]) / 2f, (_max[1] - _min[1]) / 2f));
        
        // 内側に寄せる
        // 左下
        SetPosition(0, _offset, _offset);
        SetPosition(5, _offset, _offset);
        // 左上
        SetPosition(1, _offset, -_offset);
        // 右上
        SetPosition(2, -_offset, -_offset);
        SetPosition(3, -_offset, -_offset);
        // 右下
        SetPosition(4, -_offset, _offset);

        // 上下左右のデコ
        CreateDeco();
        // 角丸部分
        CreateCircle();

        SetUV();

        helper.Clear();
        helper.AddUIVertexTriangleStream(_vertexList);
    }

    private void SetUV()
    {
        for (var i = 0; i < _vertexList.Count; i++)
        {
            var vertex = _vertexList[i];
            var uv = vertex.uv0;
            for (var j = 0; j < 2; j++)
                uv[j] = Mathf.InverseLerp(_min[j], _max[j], vertex.position[j]);

            vertex.uv0 = uv;
            _vertexList[i] = vertex;
        }
    }

    private void SetPosition(int index, float _offsetX, float _offsetY)
    {
        var vertex = _vertexList[index];
        var pos = vertex.position;
        pos += new Vector3(_offsetX, _offsetY);
        vertex.position = pos;
        _vertexList[index] = vertex;
    }

    private void CreateDeco()
    {
        // 左に伸ばす
        Deco(new []
        {
            new Vector3(_min[0], _min[1] + _offset),
            new Vector3(_min[0], _max[1] - _offset),
            new Vector3(_min[0] + _offset, _max[1] - _offset),
            new Vector3(_min[0] + _offset, _max[1] - _offset),
            new Vector3(_min[0] + _offset, _min[1] + _offset),
            new Vector3(_min[0], _min[1] + _offset),
        });
        // 右に伸ばす
        Deco(new []
        {
            new Vector3(_max[0] - _offset, _min[1] + _offset),
            new Vector3(_max[0] - _offset, _max[1] - _offset),
            new Vector3(_max[0], _max[1] - _offset),
            new Vector3(_max[0], _max[1] - _offset),
            new Vector3(_max[0], _min[1] + _offset),
            new Vector3(_max[0] - _offset, _min[1] + _offset),
        });
        // 上に伸ばす
        Deco(new []
        {
            new Vector3(_min[0] + _offset, _max[1] - _offset),
            new Vector3(_min[0] + _offset, _max[1]),
            new Vector3(_max[0] - _offset, _max[1]),
            new Vector3(_max[0] - _offset, _max[1]),
            new Vector3(_max[0] - _offset, _max[1] - _offset),
            new Vector3(_min[0] + _offset, _max[1] - _offset),
        });
        // 下に伸ばす
        Deco(new []
        {
            new Vector3(_min[0] + _offset, _min[1]),
            new Vector3(_min[0] + _offset, _min[1] + _offset),
            new Vector3(_max[0] - _offset, _min[1] + _offset),
            new Vector3(_max[0] - _offset, _min[1] + _offset),
            new Vector3(_max[0] - _offset, _min[1]),
            new Vector3(_min[0] + _offset, _min[1]),
        });
    }

    private void Deco(Vector3[] positions)
    {
        foreach (var position in positions)
        {
            _vertexList.Add(new UIVertex
            {
                position = position,
                color = _color,
            });
        }
    }

    private void CreateCircle()
    {
        // 左上
        CreateQuadCirclePoints(
            new Vector2(_min[0] + _offset, _max[1] - _offset),
            90f
        );
        // 右上
        CreateQuadCirclePoints(
            new Vector2(_max[0] - _offset, _max[1] - _offset),
            0f
        );
        // 左下
        CreateQuadCirclePoints(
            new Vector2(_min[0] + _offset, _min[1] + _offset),
            180f
        );
        // 右上
        CreateQuadCirclePoints(
            new Vector2(_max[0] - _offset, _min[1] + _offset),
            270f
        );
        
    }
    
    private void CreateQuadCirclePoints(Vector2 centerPos, float startAngle)
    {
        var points = new List<Vector2>();
        var addAngle = Mathf.CeilToInt(90f / _division);
        for (var i = 0; i < 90f; i += addAngle)
            points.Add(centerPos + GetCirclePosition(startAngle + i, _offset));
        
        points.Add(centerPos + GetCirclePosition(startAngle + 90f, _offset));
        
        for (var i = 0; i < points.Count - 1; i++)
        {
            _vertexList.Add(new UIVertex
            {
                position = new Vector3(centerPos.x, centerPos.y),
                color = _color,
            });
            _vertexList.Add(new UIVertex
            {
                position = new Vector3(points[i].x, points[i].y),
                color = _color,
            });
            _vertexList.Add(new UIVertex
            {
                position = new Vector3(points[i + 1].x, points[i + 1].y),
                color = _color,
            });
        }
    }
    
    private static Vector2 GetCirclePosition(float angle, float radius)
    {
        return new Vector2(Mathf.Cos(angle * Mathf.Deg2Rad) * radius, Mathf.Sin(angle * Mathf.Deg2Rad) * radius);
    }
}