版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/mobilebbki399/article/details/78359385
多边形雷达图作为游戏中直观体现角色各项数值的UI在许多游戏中得到应用。目前关于unity中实现雷达图的方式的教程有很多,这里提供一种UGUI实现方案,主要有以下功能:
1.支持3边以上的任意多边形。
2.使用PropertyDrawer扩展了Inspector面板,方便调节
3.支持点击穿透判定:
实现步骤:
一、实现PolygonImageEdge类,用来表示多边形雷达图的边列表,首先假设UI有N条边,其中第I条边的权重为W(0<W<=1),则可以计算出该边的顶点在局部空间中的坐标:
扫描二维码关注公众号,回复:
2959596 查看本文章
[System.Serializable]
public class PolygonImageEdge
{
public int EdgeCount
{
get
{
if (m_Weights == null)
return 0;
return m_Weights.Count;
}
}
public List<float> Weights
{
get { return m_Weights; }
}
[SerializeField] private List<float> m_Weights;
}
二、构建UIMesh:
这里通过继承MaskableGraphic类来实现UI,使用MaskableGraphic类的好处是可以受UGUI的Mask的影响。
重写OnPopulateMesh方法来根据权重生成Mesh,需要注意当边数小于3时直接执行父类方法并跳出OnPopulateMesh方法,因为不可能存在边树小于三的多边形:
protected override void OnPopulateMesh(VertexHelper vh)
{
if (edgeWeights == null || edgeWeights.EdgeCount <= 2)
{
base.OnPopulateMesh(vh);
return;
}
int edgeCount = edgeWeights.EdgeCount;
float deltaAngle = 360f/edgeCount;
vh.Clear();
for (int i = 0; i < edgeCount; i++)
{
GetTriangle(vh, i, deltaAngle);
}
}
private void GetTriangle(VertexHelper vh, int index, float deltaAngle)
{
float edgeLength = Mathf.Min(rectTransform.rect.width, rectTransform.rect.height)*0.5f;
var color32 = color;
Vector3 cent = new Vector3(0, 0);
float angle1 = 90+(index + 1)*deltaAngle;
float angle2 = 90+(index)*deltaAngle;
float radius1 = (index == edgeWeights.EdgeCount - 1 ? edgeWeights.Weights[0] : edgeWeights.Weights[index + 1])* edgeLength;
float radius2 = edgeWeights.Weights[index]*edgeLength;
Vector3 p1 = new Vector3(radius1*Mathf.Cos(angle1*Mathf.Deg2Rad), radius1*Mathf.Sin(angle1*Mathf.Deg2Rad));
Vector3 p2 = new Vector3(radius2 * Mathf.Cos(angle2 * Mathf.Deg2Rad), radius2 * Mathf.Sin(angle2 * Mathf.Deg2Rad));
vh.AddVert(cent, color32, Vector2.zero);
vh.AddVert(p1, color32, new Vector2(0,1));
vh.AddVert(p2, color32, new Vector2(1,0));
vh.AddTriangle(index*3, index*3 + 1, index*3 + 2);
}
三、点击穿透判断:
UGUI实现点击穿透判断需要实现ICanvasRaycastFilter接口,该接口包含一个public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)方法,该方法将当前鼠标的屏幕坐标传入,我们可以通过unity提供的RectTransformUtility.ScreenPointToLocalPointInRectangle方法将屏幕坐标转换到RectTransform中,接下来只需要判断该点是否处于任意一个组成多边形的三角形内即可,IsInTriangle方法用于判断点是否位于三角形内:
public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
if (raycastTarget)
{
Vector2 local;
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, sp, eventCamera, out local);
int edgeCount = edgeWeights.EdgeCount;
float deltaAngle = 360f/edgeCount;
for (int i = 0; i < edgeCount; i++)
{
bool result = IsInPolygon(i, deltaAngle, local);
if (result)
return true;
}
}
return false;
}
private bool IsInPolygon(int index, float deltaAngle, Vector2 point)
{
float edgeLength = Mathf.Min(rectTransform.rect.width, rectTransform.rect.height)*0.5f;
Vector2 cent = new Vector2(0, 0);
float angle1 = 90+(index + 1) * deltaAngle;
float angle2 = 90+(index) * deltaAngle;
float radius1 = (index == edgeWeights.EdgeCount - 1 ? edgeWeights.Weights[0] : edgeWeights.Weights[index + 1])*edgeLength;
float radius2 = edgeWeights.Weights[index]*edgeLength;
Vector2 p1 = new Vector2(radius1 * Mathf.Cos(angle1 * Mathf.Deg2Rad), radius1 * Mathf.Sin(angle1 * Mathf.Deg2Rad));
Vector2 p2 = new Vector2(radius2 * Mathf.Cos(angle2 * Mathf.Deg2Rad), radius2 * Mathf.Sin(angle2 * Mathf.Deg2Rad));
return IsInTriangle(cent, p1, p2, point);
}
private bool IsInTriangle(Vector2 vertex1, Vector2 vertex2, Vector2 vertex3, Vector2 point)
{
Vector2 v0 =vertex3 - vertex1;
Vector2 v1 = vertex2 - vertex1;
Vector2 v2 = point - vertex1;
float dot00 = Vector2.Dot(v0, v0);
float dot01 = Vector2.Dot(v0, v1);
float dot02 = Vector2.Dot(v0, v2);
float dot11 = Vector2.Dot(v1, v1);
float dot12 = Vector2.Dot(v1, v2);
float inverDeno = 1 / (dot00 * dot11 - dot01 * dot01);
float u = (dot11 * dot02 - dot01 * dot12) * inverDeno;
if (u < 0 || u > 1)
{
return false;
}
float v = (dot00 * dot12 - dot01 * dot02) * inverDeno;
if (v < 0 || v > 1)
{
return false;
}
return u + v <= 1;
}
最后通过CustomPropertyDrawer来扩展编辑器,注意这里用到了ReorderableList来绘制边:
using UnityEngine;
using UnityEditor;
using System.Collections;
using UnityEditorInternal;
[CustomPropertyDrawer(typeof(PolygonImageEdge))]
public class PolygonImageEdgeDrawer : PropertyDrawer
{
private ReorderableList m_ReorderableList;
private void Init(SerializedProperty property)
{
if (m_ReorderableList == null)
{
m_ReorderableList = new ReorderableList(property.serializedObject,
property.FindPropertyRelative("m_Weights"));
m_ReorderableList.drawElementCallback = DrawEdgeWeight;
m_ReorderableList.drawHeaderCallback = DrawHeader;
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Init(property);
var val = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
m_ReorderableList.DoList(position);
EditorGUI.indentLevel = val;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
Init(property);
return m_ReorderableList.GetHeight();
}
private void DrawEdgeWeight(Rect rect, int index, bool isActive, bool isFocused)
{
SerializedProperty itemData = m_ReorderableList.serializedProperty.GetArrayElementAtIndex(index);
EditorGUI.Slider(rect, itemData, 0, 1);
}
private void DrawHeader(Rect rect)
{
EditorGUI.LabelField(rect, "边权重");
}
}
更多文章:http://www.lsngo.net