うにてぃブログ

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

【Unity】Image を曲げる

ImageBaseMeshEffect を利用して曲げてみたかったので作成してみました

※Sliced や Filled には未対応、水平のみ

f:id:hacchi_man:20210414010133p:plain:w400

メッシュの分割数や、外周の長さなどパラメータの調整をすることで
曲げ具合を変更することができます

f:id:hacchi_man:20210414010311p:plain:w400

Shader を使えば Sliced などのときもうまくいくと思うので、
どんな画像でもきれいに曲げたい場合は Shader を利用してください

コード

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
 
/// <summary>
/// Graphic でもいいけど
/// 頂点の順番がものによって異なるのでImage限定
/// </summary>
[RequireComponent(typeof(Image))]
public class CurveImage : BaseMeshEffect
{
    [SerializeField, Range(1, 50)]
    private int _divide = 10;
    [SerializeField]
    private float _radius = 1f;
    [SerializeField, Range(0 , 180f)]
    private float _outerLength = 10;
 
    private readonly List<UIVertex> _vertexList = new List<UIVertex>();
 
    private float _posMin;
    private float _posMax;
    private float _uvMin;
    private float _uvMax;
    private Vector2 _size;
 
    public override void ModifyMesh(VertexHelper helper)
    {
        if (_divide <= 0)
            return;
 
        _vertexList.Clear();
        helper.GetUIVertexStream(_vertexList);
 
        var rect = transform as RectTransform;
        _size = new Vector2(rect.rect.width, rect.rect.height);
 
        // 分割分頂点を複製
        for (var i = 0; i < _divide; i++)
        {
            for (var j = 0; j < 6; j++)
            {
                _vertexList.Add(_vertexList[j]);
            }
        }
 
        _posMin = _vertexList.Min(v => v.position.x);
        _posMax = _vertexList.Max(v => v.position.x);
        _uvMin = _vertexList.Min(v => v.uv0.x);
        _uvMax = _vertexList.Max(v => v.uv0.x);

        SetMeshesRect();
        SetMeshesCurve();

        helper.Clear();
        helper.AddUIVertexTriangleStream(_vertexList);
    }
 
    /// <summary>
    /// 全頂点を長方形に配置
    /// </summary>
    private void SetMeshesRect()
    {
        // 一つあたりの距離
        var offset = (_posMax - _posMin) / (_divide + 1);
        var x = _posMin;

        // 頂点の位置を変動
        for (var i = 0; i < _vertexList.Count; i += 6)
        {
            var t = Mathf.InverseLerp(_posMin, _posMax, x);
            // 左側
            foreach (var index in new []{0, 1, 5})
            {
                SetMeshRect(i + index, t);
            }

            x += offset;
            t = Mathf.InverseLerp(_posMin, _posMax, x);
            // 右側
            foreach (var index in new[] {2, 3, 4})
            {
                SetMeshRect(i + index, t);
            }
        }
    }
 
    /// <summary>
    /// メッシュを長方形に配置
    /// </summary>
    private void SetMeshRect(int index, float t)
    {
        var vertex = _vertexList[index];
        var p = vertex.position;
        p.x = Mathf.Lerp(_posMin, _posMax, t);
        vertex.position = p;
        var uv0 = vertex.uv0;
        uv0.x = Mathf.Lerp(_uvMin, _uvMax, t);
        vertex.uv0 = uv0;
        _vertexList[index] = vertex;
    }
 
    /// <summary>
    /// 長方形に配置した頂点をカーブさせる
    /// </summary>
    private void SetMeshesCurve()
    {
        for (var i = 0; i < _vertexList.Count; i += 6)
        {
            // 下
            foreach (var index in new []{0, 4, 5})
                SetMeshCurve(i + index, false);

            // 上
            foreach (var index in new[] {1, 2, 3})
                SetMeshCurve(i + index, true);
        }
    }
 
    /// <summary>
    /// 長方形の配置は終わったので、カーブの配置設定
    /// </summary>
    private void SetMeshCurve(int index, bool isTop)
    {
        var vertex = _vertexList[index];
        var p = vertex.position;
 
        var t = Mathf.InverseLerp(_posMin, _posMax, p.x);
        var angle = 90 + Mathf.Lerp(_outerLength / 2f, -_outerLength / 2f, t);
 
        // 上部はxをずらす必要がある
        if (isTop)
        {
            var radius = _radius + _size.y;
            // 下
            var bottom = GetCirclePosition(angle, _radius);
            // 上
            var top = GetCirclePosition(angle, radius);
            // 差分分だけ移動させる
            p.x -= bottom.x - top.x;
            p.y = p.y - radius + top.y;
        }
        else
        {
            var pos = GetCirclePosition(angle, _radius);
            p.y = p.y - _radius + pos.y;
        }
 
        vertex.position = p;
        _vertexList[index] = vertex;
    }
 
    /// <summary>
    /// 周囲の位置を取得
    /// </summary>
    private Vector2 GetCirclePosition(float angle, float radius)
    {
        return new Vector2(
            Mathf.Cos(angle * Mathf.Deg2Rad) * radius,
            Mathf.Sin(angle * Mathf.Deg2Rad) * radius
        );
    }
}