Unity中的曲线绘制(一)——直线
发表于2016-11-02
直线
让我们从创建一条简单的直线开始。两点成线,我们需要一个起始点p0,以及终点p2;
现在我们可以创建一个游戏对象,为它绑定Line脚本,并且设置两个点的坐标。目前我们在scene视图中还看不见这些点。当我们选中Line对象时,希望能够像选中别的游戏对象一样,在scene视图中显示Line的形状。这点可以通过创建自定义的inspector来实现。
与Editor有关的脚本必须放在Editor文件夹下,在Assets目录下创建一个Editor文件夹,并且在它里面新建一个LineInspector脚本。
自定义的inspector必须继承UnityEditor.Editor。并且要为它添加UnityEditor.CustomEditor属性。这样Unity才知道选中Line的时候要使用我们自定义的editor。
1 2 3 4 5 6 | using UnityEditor; using UnityEngine; [CustomEditor( typeof (Line))] public class LineInspector : Editor { } |
要使editor起作用,必须为它添加OnSceneGUI方法。我们需要在这个方法里相关的代码来在scene视图中绘制元素。
Editor类有一个target变量,这个变量代表的是鼠标选中的对象(Hieraychy或者scene中)。我们把它的类型转换为Line,然后使用Handles工具类来在指定点之间绘制直线。
1 2 3 4 5 6 | private void OnSceneGUI () { Line line = target as Line; Handles.color = Color.white; Handles.DrawLine(line.p0, line.p1); } |
现在我们可以在scene视图中看见一条直线了,但是它是固定的,移动、旋转、缩放Line对象,这条直线是完全不会变的。这是由于Handles是在世界坐标系下进行操作,而我们的两个点的坐标位于相对于Line对象的局部坐标系。因此我们需要将p0和p1的坐标转换为世界坐标后再使用Handles工具类来绘制。
1 2 3 4 5 6 7 8 9 | private void OnSceneGUI () { Line line = target as Line; Transform handleTransform = line.transform; Vector3 p0 = handleTransform.TransformPoint(line.p0); Vector3 p1 = handleTransform.TransformPoint(line.p1); Handles.color = Color.white; Handles.DrawLine(p0, p1); } |
(左图为转换前,右图为转换后)
除了显示直线,我们还可以为直线上的两个点提供坐标轴示意(红蓝绿轴)。要实现这一点,我们还需要获取transform的旋转。
1 2 3 4 5 6 7 8 9 10 11 12 | private void OnSceneGUI () { Line line = target as Line; Transform handleTransform = line.transform; Quaternion handleRotation = handleTransform.rotation; Vector3 p0 = handleTransform.TransformPoint(line.p0); Vector3 p1 = handleTransform.TransformPoint(line.p1); Handles.color = Color.white; Handles.DrawLine(p0, p1); Handles.DoPositionHandle(p0, handleRotation); Handles.DoPositionHandle(p1, handleRotation); } |
1 2 | Quaternion handleRotation = Tools.pivotRotation == PivotRotation.Local ? handleTransform.rotation : Quaternion.identity; |
平时使用Unity时,在scene视图中移动对象的坐标轴,对象的transform值就会发生改变。在这里,我们希望实现,移动两点的坐标轴,Line脚本中它们的值也会发生改变。同样地,由于坐标轴的坐标是在世界坐标系下,我们需要用InverseTransformPoint方法把坐标转换为Line脚本绑定对象的局部坐标系。为了实时反馈点位置的改变,我们需要使用EditorGUI.BeginChangeCheck方法和EditorGUI.EndChangeCheck方法。当p0对应坐标轴被我们拖动改变后,EditorGUI.EndChangeCheck()方法会返回true,此时就可以将其坐标进行转换,修改脚本中p0的值。
1 2 3 4 5 6 7 8 9 10 | EditorGUI.BeginChangeCheck(); p0 = Handles.DoPositionHandle(p0, handleRotation); if (EditorGUI.EndChangeCheck()) { line.p0 = handleTransform.InverseTransformPoint(p0); } EditorGUI.BeginChangeCheck(); p1 = Handles.DoPositionHandle(p1, handleRotation); if (EditorGUI.EndChangeCheck()) { line.p1 = handleTransform.InverseTransformPoint(p1); } |
现在我们可以在scene视图中拖动我们的直线和点了。
但是仍然有两个小问题。
1、不能撤销点的拖动(windows下为ctrl+z)。
2、Unity不会记录改变,退出的时候不会提示保存信息。
第一个问题解决方法是,在修改点的位置之前,调用Undo.RecordObject方法;第二个问题通过调用EditorUtility.SetDirty解决。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | EditorGUI.BeginChangeCheck(); p0 = Handles.DoPositionHandle(p0, handleRotation); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(line, "Move Point" ); EditorUtility.SetDirty(line); line.p0 = handleTransform.InverseTransformPoint(p0); } EditorGUI.BeginChangeCheck(); p1 = Handles.DoPositionHandle(p1, handleRotation); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(line, "Move Point" ); EditorUtility.SetDirty(line); line.p1 = handleTransform.InverseTransformPoint(p1); } |