IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity中实现雷达图 -> 正文阅读

[游戏开发]Unity中实现雷达图

一:效果演示


二:实现思路

——描边思路:一开始觉得这很简单,就直接绘制一个比当前大一圈的雷达图就可以了,试了试发现并没有那么简单
当每个顶点的权重都相同时,也就是雷达图为正多边形时绘制出来的描边是等宽的,但是因为雷达图每个顶点的权重是不均匀的,所以会导致描边的宽度也不均匀,效果如下

解决办法是对于每一个顶点,都找出上一个顶点和下一个顶点,并且得到它相邻的两条边的向外的垂线,沿着垂线向外延伸相同宽度后得到两个点,再把两个点沿着边的方向延伸相交于一点,对每个顶点重复上述步骤,就能得到描边外框的所有点了

例如对于顶点B,找到上一个顶点A和下一个顶点C,沿着垂线向外延伸相同宽度后得到两个点A1和C1,再将A1和C1沿着边的方向延伸相交与点B1,再对顶点C进行同样的操作找到交点C1.....

——求每条边向外的垂线方向向量:因为是求某条边的垂直向量,所以可以利用两个向量的点乘结果为0的性质来求,但是每条边的垂直向量都有两个,我们需要知道哪个是向外的垂直方向,经过如下推导可求得

对于顶点A,向量DA的符号为(+,+),向外垂直向量DD1的符号为(+,-),向量BA的符号为(+,-),向外的垂直向量BB1的符号为(+,+)
对于顶点B,向量AB的符号为(-,+),向外垂直向量AA1的符号为(+,+),向量CB的符号为(+,+),向外的垂直向量CC1的符号为(-,+)
结论:对于向量为(x,y),上一个顶点的边的向外垂直向量为(y,-x)。下一个顶点的边的向外垂直向量为(-y,x)


三:使用

——面板参数设置

Sprite:雷达图内部图片
Color:颜色
Raycast Target:是否接收射线
Radius:半径
Side Count:几边形(有几个顶点)
Show Inner:是否显示雷达图内部
Show Outline:是否显示雷达图描边
OutlineData-Width:描边宽度
OutlineData-Color:描边颜色


——常规使用
雷达图的背景一般都是美术提供素材,调整RadarChart的Radius大小使雷达图与美术素材一致并设置RadarChart组件的相关参数

SetRadarChart:设置雷达图
SetRatioList:设置雷达图每个顶点的比例值

using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    public RadarChart radarChart;


    private void Awake()
    {
        List<float> ratioList = new List<float>() { 0.5f, 0.4f, 0.3f, 0.5f, 0.7f, 1f };

        radarChart.SetRadarChart();
        radarChart.SetRatioList(ratioList);
    }
}

四:代码实现

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

/// <summary>
/// 雷达图组件
/// </summary>
[AddComponentMenu("LFramework/UI/RadarChart", 51)]
public class RadarChart : MaskableGraphic
{
    protected RadarChart()
    {

    }

    /// <summary>
    /// 描边数据
    /// </summary>
    [Serializable]
    public class OutlineData
    {
        [SerializeField]
        public float width = 5;
        [SerializeField]
        public Color color = Color.red;
    }

    //Sprite图片
    [SerializeField]
    Sprite m_Sprite;
    public Sprite Sprite
    {
        get { return m_Sprite; }
    }

    //贴图
    public override Texture mainTexture
    {
        get
        {
            if (m_Sprite == null)
            {
                if (material != null && material.mainTexture != null)
                {
                    return material.mainTexture;
                }
                return s_WhiteTexture;
            }

            return m_Sprite.texture;
        }
    }

    //半径
    [SerializeField]
    float m_Radius = 100;

    //边数(几边形)
    [SerializeField]
    int m_SideCount;
    public int SideCount
    {
        get
        {
            m_SideCount = Mathf.Clamp(m_SideCount, 3, 65000);
            return m_SideCount;
        }
    }

    //是否显示雷达图内部
    [SerializeField]
    bool m_ShowInner = true;

    //是否显示雷达图描边
    [SerializeField]
    bool m_ShowOutline;

    //雷达图描边数据
    [SerializeField]
    OutlineData m_OutlineData;

    //比例值列表
    List<float> m_RatioList = new List<float>();

    //顶点位置列表
    List<Vector3> m_TempVertexList = new List<Vector3>();
    List<Vector3> m_VertexList = new List<Vector3>();

    //比例值变化后
    public Action OnRatioValueChanged;

    /// <summary>
    /// 初始化比例值列表
    /// </summary>
    void InitRatioList()
    {
        int ratioCount = m_RatioList.Count;
        if (ratioCount < SideCount)
        {
            for (int i = 0; i < SideCount - ratioCount; i++)
            {
                m_RatioList.Add(1);
            }
        }
    }

    /// <summary>
    /// 设置比例值列表
    /// </summary>
    public void SetRatioList(List<float> ratioList)
    {
        for (int i = 0; i < m_RatioList.Count; i++)
        {
            if (ratioList.Count - 1 >= i)
            {
                m_RatioList[i] = ratioList[i];
            }
        }
        SetVerticesDirty();
        CalcVertexPos();

        OnRatioValueChanged?.Invoke();
    }

    /// <summary>
    /// 设置雷达图
    /// </summary>
    public void SetRadarChart()
    {
        rectTransform.sizeDelta = new Vector2(m_Radius * 2, m_Radius * 2);
        InitRatioList();
    }

    /// <summary>
    /// 得到比例值列表
    /// </summary>
    public List<float> GetRatioList()
    {
        return m_RatioList;
    }

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh.Clear();
        m_TempVertexList.Clear();

        GenerateInner(vh);
        if (m_ShowOutline)
        {
            GenerateOutline(vh);
        }
    }

    /// <summary>
    /// 生成雷达图内部
    /// </summary>
    void GenerateInner(VertexHelper vh)
    {
        Vector4 uv = m_Sprite == null
             ? Vector4.zero
             : DataUtility.GetOuterUV(m_Sprite);
        float uvWidth = uv.z - uv.x;
        float uvHeight = uv.w - uv.y;
        float diameter = m_Radius * 2;
        Vector2 uvCenter = new Vector2((uv.x + uv.z) * 0.5f, (uv.y + uv.w) * 0.5f);
        Vector3 posCenter = new Vector2((0.5f - rectTransform.pivot.x) * diameter, (0.5f - rectTransform.pivot.y) * diameter);
        float uvScaleX = uvWidth / diameter;
        float uvScaleY = uvHeight / diameter;
        float deltaRad = 2 * Mathf.PI / SideCount;

        float curRad = 0;
        int vertexCount = SideCount + 1;
        int triangleCount = SideCount;

        UIVertex vertex = new UIVertex();
        vh.AddVert(posCenter, color, uvCenter);
        for (int i = 0; i < vertexCount - 1; i++)
        {
            float r = m_RatioList.Count <= i
                     ? m_Radius
                     : m_RatioList[i] == 0 ? m_Radius : m_Radius * m_RatioList[i];
            Vector3 posOffset = new Vector3(r * Mathf.Cos(curRad), r * Mathf.Sin(curRad));
            vertex.position = posCenter + posOffset;
            vertex.color = color;
            vertex.uv0 = new Vector2(uvCenter.x + posOffset.x * uvScaleX, uvCenter.y + posOffset.y * uvScaleY);
            vh.AddVert(vertex);
            m_TempVertexList.Add(vertex.position);

            curRad += deltaRad;
        }

        if (m_ShowInner)
        {
            for (int i = 0; i < triangleCount; i++)
            {
                vh.AddTriangle(0, i + 1, i + 2 >= vertexCount ? 1 : i + 2);
            }
        }
    }

    /// <summary>
    /// 生成雷达图描边
    /// </summary>
    void GenerateOutline(VertexHelper vh)
    {
        int vertexCount = m_TempVertexList.Count + 1;
        int triangleCount = m_TempVertexList.Count * 2;
        for (int i = 0; i < m_TempVertexList.Count; i++)
        {
            Vector2 curPos = m_TempVertexList[i];
            Vector2 prePos = i - 1 < 0 ? m_TempVertexList[m_TempVertexList.Count - 1] : m_TempVertexList[i - 1];
            Vector2 nextPos = m_TempVertexList[(i + 1) % m_TempVertexList.Count];
            Vector2 dir1 = (curPos - prePos).normalized;
            Vector2 dir2 = (curPos - nextPos).normalized;
            Vector2 normal1 = GetNormal(dir1);
            Vector2 normal2 = GetNormal(-dir2);
            Vector2 pos1 = prePos + normal1 * m_OutlineData.width;
            Vector2 pos2 = nextPos + normal2 * m_OutlineData.width;
            Vector2 crossPoint = GetCrossPoint(pos1, dir1, pos2, dir2);

            vh.AddVert(curPos, m_OutlineData.color, Vector2.zero);
            vh.AddVert(crossPoint, m_OutlineData.color, Vector2.zero);
        }

        for (int i = vertexCount; i < m_TempVertexList.Count * 3 + 1; i += 2)
        {
            vh.AddTriangle(i, i + 1, i + 3 >= m_TempVertexList.Count * 3 + 1 ? m_TempVertexList.Count + 2 : i + 3);
            vh.AddTriangle(i, i + 2 >= m_TempVertexList.Count * 3 + 1 ? m_TempVertexList.Count + 1 : i + 2, i + 3 >= m_TempVertexList.Count * 3 + 1 ? m_TempVertexList.Count + 2 : i + 3);
        }
    }

    /// <summary>
    /// 得到法线
    /// </summary>
    Vector2 GetNormal(Vector2 dir)
    {
        return new Vector2(dir.y, -dir.x);
    }

    //误差范围
    const float ERROR_RANGE = 0.001f;
    /// <summary>
    /// 得到交点
    /// </summary>
    Vector2 GetCrossPoint(Vector2 pos1, Vector2 dir1, Vector2 pos2, Vector2 dir2)
    {
        bool parallelToY1 = false;
        bool parallelToY2 = false;

        float k1;
        float k2;
        if (Mathf.Abs(dir1.x) <= ERROR_RANGE
            || Mathf.Abs(dir1.y) <= ERROR_RANGE)
        {
            k1 = 0;
            if (Mathf.Abs(dir1.x) <= ERROR_RANGE)
            {
                parallelToY1 = true;
            }
        }
        else
        {
            k1 = dir1.y / dir1.x;
        }
        if (Mathf.Abs(dir2.x) <= ERROR_RANGE
            || Mathf.Abs(dir2.y) <= ERROR_RANGE)
        {
            k2 = 0;
            if (Mathf.Abs(dir2.x) <= ERROR_RANGE)
            {
                parallelToY2 = true;
            }
        }
        else
        {
            k2 = dir2.y / dir2.x;
        }
        float b1 = pos1.y - k1 * pos1.x;
        float b2 = pos2.y - k2 * pos2.x;
        if (parallelToY1)
        {
            float x = pos1.x;
            float y = k2 * x + b2;
            return new Vector2(x, y);
        }
        else if (parallelToY2)
        {
            float x = pos2.x;
            float y = k1 * x + b1;
            return new Vector2(x, y);
        }
        else
        {
            float x = (b2 - b1) / (k1 - k2);
            float y = k1 * x + b1;
            return new Vector2(x, y);
        }
    }

    /// <summary>
    /// 计算顶点位置
    /// </summary>
    void CalcVertexPos()
    {
        m_VertexList.Clear();
        float diameter = m_Radius * 2;
        Vector3 posCenter = new Vector2((0.5f - rectTransform.pivot.x) * diameter, (0.5f - rectTransform.pivot.y) * diameter);
        float deltaRad = 2 * Mathf.PI / SideCount;

        float curRad = 0;
        for (int i = 0; i < SideCount; i++)
        {
            float r = m_RatioList.Count <= i
                     ? m_Radius
                     : m_RatioList[i] == 0 ? m_Radius : m_Radius * m_RatioList[i];
            Vector3 pos = posCenter + new Vector3(r * Mathf.Cos(curRad), r * Mathf.Sin(curRad));
            m_VertexList.Add(pos);

            curRad += deltaRad;
        }
    }
}

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-01-29 23:24:16  更:2022-01-29 23:24:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 18:45:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码