原因
在使用 Unity 编辑器开发一些功能辅助的时候,想要在地面等其他网格上进行踩点,但是这些网格并没有碰撞体组件,所以只能寻找其他方式来达到鼠标所在即是网格上的点。
刚好看到一个开源项目达到了这个要求,项目地址 https://github.com/slipster216/VertexPaint,里面的视频效果如下:
实现
首先,先实现鼠标所在位置有个球进行跟随,这样才好进行下一步的踩点操作。创建一个编辑器类,代码如下:
using UnityEditor;
using UnityEngine;
public class SceneMouseWindow : EditorWindow
{
[MenuItem("Tool/Window/Scene Mouse")]
public static void ShowWindow()
{
var window = GetWindow<SceneMouseWindow>();
window.Show();
}
void OnFocus()
{
SceneView.onSceneGUIDelegate -= OnSceneGUI;
SceneView.onSceneGUIDelegate += OnSceneGUI;
Repaint();
}
void OnDestroy()
{
SceneView.onSceneGUIDelegate -= OnSceneGUI;
}
private void OnSceneGUI(SceneView sceneView)
{
// 当前屏幕坐标,左上角是(0,0)右下角(camera.pixelWidth,camera.pixelHeight)
Vector2 mousePosition = Event.current.mousePosition;
// Retina 屏幕需要拉伸值
float mult = 1;
#if UNITY_5_4_OR_NEWER
mult = EditorGUIUtility.pixelsPerPoint;
#endif
// 转换成摄像机可接受的屏幕坐标,左下角是(0,0,0)右上角是(camera.pixelWidth,camera.pixelHeight,0)
mousePosition.y = sceneView.camera.pixelHeight - mousePosition.y * mult;
mousePosition.x *= mult;
// 近平面往里一些,才能看得到摄像机里的位置
Vector3 fakePoint = mousePosition;
fakePoint.z = 20;
Vector3 point = sceneView.camera.ScreenToWorldPoint(fakePoint);
Handles.SphereCap(0, point, Quaternion.identity, 2);
// 刷新界面,才能让球一直跟随
sceneView.Repaint();
HandleUtility.Repaint();
}
}
运行效果如下所示:
网格
通过鼠标所在位置与网格进行射线检测,需要编辑器的隐藏接口HandleUtility.IntersectRayMesh
,这个接口没有公开,所以只能反射获取,代码参照 https://gist.github.com/MattRix/9205bc62d558fef98045,另外,使用Handles.SphereCap
进行绘制球体的话,没法跟网格进行交叉,无法很好的确定交点的位置,所以这里改成创建一个球体物体,代码如下:
private void OnSceneGUI(SceneView sceneView)
{
// 省略...
Ray ray = sceneView.camera.ScreenPointToRay(mousePosition);
MeshFilter[] componentsInChildren = GameObject.FindObjectsOfType<MeshFilter>();
float num = float.PositiveInfinity;
foreach (MeshFilter meshFilter in componentsInChildren)
{
Mesh sharedMesh = meshFilter.sharedMesh;
RaycastHit hit;
if (sharedMesh
&& RXLookingGlass.IntersectRayMesh(ray, sharedMesh, meshFilter.transform.localToWorldMatrix, out hit)
&& hit.distance < num)
{
point = hit.point;
num = hit.distance;
}
}
//Handles.SphereCap(0, point, Quaternion.identity, 2);
SphereCapPos(point);
// 省略...
}
private static Transform capSphere;
private void SphereCapPos(Vector3 point)
{
if (capSphere == null)
{
GameObject go = GameObject.Find("[SphereCapPos]");
if (go == null)
{
go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.name = "[SphereCapPos]";
Collider collider = go.GetComponent<Collider>();
DestroyImmediate(collider);
Material mat = new Material(Shader.Find("Unlit/Color"));
mat.SetColor("_Color", Color.cyan);
mat.hideFlags = HideFlags.HideAndDontSave;
Renderer renderer = go.GetComponent<Renderer>();
renderer.sharedMaterial = mat;
}
go.hideFlags = HideFlags.HideAndDontSave;
capSphere = go.transform;
capSphere.rotation = Quaternion.identity;
capSphere.localScale = Vector3.one * 0.5f;
}
capSphere.position = point;
}
运行效果如下所示: