UGUI—能力雷达地图

作者:追风剑情 发布于:2025-4-23 23:45 分类:Unity3d

一、工程截图

2222.png

二、代码

using System;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 能力雷达地图
/// </summary>
[ExecuteInEditMode]
public class UIAbilityRadarGraph : MonoBehaviour
{
    [Serializable]
    public class AbilityVector
    {
        public RectTransform rectTransform;
        [Range(0.01f, 1.0f)]
        public float value;
    }

    //特别注意:数组里的对象一定要顺时针排列
    [Tooltip("能力向量对象")]
    public AbilityVector[] abilityVectors;
    [Tooltip("内部填充颜色")]
    public Color innerColor = Color.blue;
    [Tooltip("边框颜色")]
    public Color borderColor = Color.red;
    [Tooltip("边框宽度(单位:像素)")]
    public int borderWidth = 10;

    private RectTransform rectTransform;
    private RawImage rawImage;
    private float radius;
    private Vector2[] directions;

    private void Awake()
    {
        rectTransform = this.GetComponent<RectTransform>();
        rawImage = GetComponent<RawImage>();
        radius = rectTransform.sizeDelta.x / 2;
        UpdateAbility();
    }

    private void Start()
    {
        Draw(directions, innerColor, borderColor);
    }

    private void UpdateAbility()
    {
        if (abilityVectors != null && abilityVectors.Length > 0)
        {
            directions = new Vector2[abilityVectors.Length];
            for (int i = 0; i < abilityVectors.Length; i++)
            {
                var ab = abilityVectors[i];
                var pos = ab.rectTransform.anchoredPosition;
                directions[i] = pos.normalized * ab.value;
            }
        }
    }

    /// <summary>
    /// 绘制能力雷达图
    /// </summary>
    /// <param name="values">能力值数组,取值范围[0,1]</param>
    public void Draw(float[] values)
    {
        if (abilityVectors == null)
            return;
        for (int i = 0; i < abilityVectors.Length; i++)
        {
            if (i >= values.Length)
                break;
            float value = values[i];
            value = Mathf.Clamp(value, 0.1f, 1.0f);
            abilityVectors[i].value = value;
        }
        UpdateAbility();
        Draw(directions, innerColor, borderColor);
    }

    /// <summary>
    /// 绘制能力雷达图
    /// </summary>
    /// <param name="directions">能力方向向量</param>
    /// <param name="innerColor">内部颜色</param>
    /// <param name="borderColor">边框颜色</param>
    public void Draw(Vector2[] directions, Color innerColor, Color borderColor)
    {
        if (directions == null || directions.Length <= 2)
            return;
        //归一化
        foreach (var dv in directions)
            dv.Normalize();
        //创建Texture2D
        RectTransform rt = rectTransform;
        if (rt == null)
            return;
        int w = (int)rt.sizeDelta.x;
        int h = (int)rt.sizeDelta.y;
        Texture2D tex = new Texture2D(w, h, TextureFormat.ARGB32, false);
        //构造边向量
        Vector2[] edges = new Vector2[directions.Length];
        for(int i=1; i<directions.Length; i++)
        {
            Vector2 v0 = directions[i-1];
            Vector2 v1 = directions[i];
            //顺时针构造边向量
            edges[i-1] = v1 - v0;
        }
        //构造最后一条边向量
        Vector2 firstV = directions[0];
        Vector2 lastV = directions[directions.Length-1];
        edges[edges.Length-1] = firstV - lastV;

        Vector2 v = Vector2.zero;
        //遍历Texture2D
        for (int y = 0; y < h; y++)
        {
            for (int x = 0; x < w; x++)
            {
                //将区间转为 [-0.5, 0.5]
                v.x = (float)x / (float)w - 0.5f;
                v.y = (float)y / (float)h - 0.5f;
                //将区间转为 [-1, 1]
                v.x *= 2;
                v.y *= 2;
                bool inner = IsInner(v, directions, edges);
                bool innerBorder = IsInnerBoder(v, directions, edges);
                if (inner)
                {
                    Color c = innerBorder ? borderColor : innerColor;
                    tex.SetPixel(x, y, c);
                }
                else
                {
                    tex.SetPixel(x, y, Color.clear);
                }
            }
        }
        tex.Apply();
        rawImage.texture = tex;
    }

    // 判断像素点是否在多边形内部
    private bool IsInner(Vector2 v, Vector2[] directions, Vector2[] edges)
    {
        for(int i = 0; i < edges.Length; i++)
        {
            Vector2 ev = edges[i];
            Vector2 dv = directions[i];
            Vector2 pv = v - dv;
            if (CrossProduct(ev, pv) > 0)
                return false;
        }
        return true;
    }

    // 向量叉乘
    private float CrossProduct(Vector2 v1, Vector2 v2)
    {
        return v1.x * v2.y - v2.x * v1.y;
    }

    // 是否在border内
    private bool IsInnerBoder(Vector2 v, Vector2[] directions, Vector2[] edges)
    {
        float d = MinEdgeDistance(v, directions, edges);
        return d <= borderWidth;
    }

    // 计算离边缘的最小距离
    private float MinEdgeDistance(Vector2 v, Vector2[] directions, Vector2[] edges)
    {
        float min_d = float.MaxValue;
        for (int i = 0; i < edges.Length; i++)
        {
            Vector2 ev = edges[i];
            Vector2 dv = directions[i];
            Vector2 pv = v - dv;
            float d = Distance(ev, pv);
            if (d < min_d) min_d = d;
        }
        return min_d;
    }

    // 计算离指定边的距离
    private float Distance(Vector2 edge, Vector2 pv)
    {
        float angle = Vector2.Angle(edge, pv);
        //弧度角
        float radian = angle * Mathf.Deg2Rad;
        //此时的d是归一化的
        float d = pv.magnitude * Mathf.Sin(radian);
        //转成像素尺寸
        d = d * radius;
        return d;
    }

    // Inspector上的值发生变化时调用
    private void OnValidate()
    {
        UpdateAbility();
        Draw(directions, innerColor, borderColor);
    }
}

运行效果

111.gif

标签: Unity3d

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号