Unity3D 教你制作Bezier和Spine曲线编辑器三

发表于2017-09-18
评论0 1.3k浏览

继续接着介绍曲线编辑器的制作,上篇博客介绍了关于Bezier曲线的制作,接下来给读者介绍Spine B样条曲线之作。

如果要创建复杂的曲线,我们需要连接多个曲线,这样的构造称为样条。让我们通过复制Bezier曲线代码来创建一个,将类型更改为BezierSpline。

[csharp] view plain copy
  1. using UnityEngine;  
  2.   
  3. public class BezierSpline : MonoBehaviour {  
  4.   
  5.     public Vector3[] points;  
  6.       
  7.     public Vector3 GetPoint (float t) {  
  8.         return transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], points[3], t));  
  9.     }  
  10.       
  11.     public Vector3 GetVelocity (float t) {  
  12.         return transform.TransformPoint(  
  13.             Bezier.GetFirstDerivative(points[0], points[1], points[2], points[3], t)) - transform.position;  
  14.     }  
  15.       
  16.     public Vector3 GetDirection (float t) {  
  17.         return GetVelocity(t).normalized;  
  18.     }  
  19.       
  20.     public void Reset () {  
  21.         points = new Vector3[] {  
  22.             new Vector3(1f, 0f, 0f),  
  23.             new Vector3(2f, 0f, 0f),  
  24.             new Vector3(3f, 0f, 0f),  
  25.             new Vector3(4f, 0f, 0f)  
  26.         };  
  27.     }  
  28. }  

另外我们还通过复制和调整Bezier曲线Inspector的代码来创建一个编辑器,然后我们可以创建一个spline对象并编辑它,就像一条曲线。

[csharp] view plain copy
  1. using UnityEditor;  
  2. using UnityEngine;  
  3.   
  4. [CustomEditor(typeof(BezierSpline))]  
  5. public class BezierSplineInspector : Editor {  
  6.   
  7.     private const int lineSteps = 10;  
  8.     private const float directionScale = 0.5f;  
  9.   
  10.     private BezierSpline spline;  
  11.     private Transform handleTransform;  
  12.     private Quaternion handleRotation;  
  13.   
  14.     private void OnSceneGUI () {  
  15.         spline = target as BezierSpline;  
  16.         handleTransform = spline.transform;  
  17.         handleRotation = Tools.pivotRotation == PivotRotation.Local ?  
  18.             handleTransform.rotation : Quaternion.identity;  
  19.           
  20.         Vector3 p0 = ShowPoint(0);  
  21.         Vector3 p1 = ShowPoint(1);  
  22.         Vector3 p2 = ShowPoint(2);  
  23.         Vector3 p3 = ShowPoint(3);  
  24.           
  25.         Handles.color = Color.gray;  
  26.         Handles.DrawLine(p0, p1);  
  27.         Handles.DrawLine(p2, p3);  
  28.           
  29.         ShowDirections();  
  30.         Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);  
  31.     }  
  32.   
  33.     private void ShowDirections () {  
  34.         Handles.color = Color.green;  
  35.         Vector3 point = spline.GetPoint(0f);  
  36.         Handles.DrawLine(point, point   spline.GetDirection(0f) * directionScale);  
  37.         for (int i = 1; i <= lineSteps; i ) {  
  38.             point = spline.GetPoint(i / (float)lineSteps);  
  39.             Handles.DrawLine(point, point   spline.GetDirection(i / (float)lineSteps) *   
[csharp] view plain copy
  1. <span style="white-space:pre">      </span> directionScale);  
  2.         }  
  3.     }  
  4.   
  5.     private Vector3 ShowPoint (int index) {  
  6.         Vector3 point = handleTransform.TransformPoint(spline.points[index]);  
  7.         EditorGUI.BeginChangeCheck();  
  8.         point = Handles.DoPositionHandle(point, handleRotation);  
  9.         if (EditorGUI.EndChangeCheck()) {  
  10.             Undo.RecordObject(spline, "Move Point");  
  11.             EditorUtility.SetDirty(spline);  
  12.             spline.points[index] = handleTransform.InverseTransformPoint(point);  
  13.         }  
  14.         return point;  
  15.     }  
  16. }  


这样,让我们向BezierSpline添加一个方法,以向样条添加另一条曲线,因为我们希望spline是连续的,上一条曲线的最后一点与下一条曲线的第

一个点是一样的。所以每条额外的曲线又增加了三个点。

[csharp] view plain copy
  1. public void AddCurve () {  
  2.         Vector3 point = points[points.Length - 1];  
  3.         Array.Resize(ref points, points.Length   3);  
  4.         point.x  = 1f;  
  5.         points[points.Length - 3] = point;  
  6.         point.x  = 1f;  
  7.         points[points.Length - 2] = point;  
  8.         point.x  = 1f;  
  9.         points[points.Length - 1] = point;  
  10.     }  

我们使用Array.Resize创建一个更大的数组来保存新点,它在system命名空间内,因此我们应该声明我们在脚本

的顶部使用它。

[csharp] view plain copy
  1. using UnityEngine;  
  2. using System;  

为了能够添加一条曲线,我们必须在spline的检查器中添加一个按钮,我们可以通过重写BezierSplineInspector的OnInspectorGUI方法来定

制组件的统一使用。注意,这不是一个特殊的统一方法,它依赖于继承。

另外,我们调用DrawDefaultInspector方法。然后我们使用GUILayout来绘制一个按钮,点击添加一条曲线。

[csharp] view plain copy
  1. public override void OnInspectorGUI () {  
  2.     DrawDefaultInspector();  
  3.     spline = target as BezierSpline;  
  4.     if (GUILayout.Button("Add Curve")) {  
  5.         Undo.RecordObject(spline, "Add Curve");  
  6.         spline.AddCurve();  
  7.         EditorUtility.SetDirty(spline);  
  8.     }  
  9. }  



当然,我们仍然只看到第一条曲线。所以我们调整BezierSplineInspector,让它在所有曲线上循环。

[csharp] view plain copy
  1. private void OnSceneGUI () {  
  2.         spline = target as BezierSpline;  
  3.         handleTransform = spline.transform;  
  4.         handleRotation = Tools.pivotRotation == PivotRotation.Local ?  
  5.             handleTransform.rotation : Quaternion.identity;  
  6.           
  7.         Vector3 p0 = ShowPoint(0);  
  8.         for (int i = 1; i < spline.points.Length; i  = 3) {  
  9.             Vector3 p1 = ShowPoint(i);  
  10.             Vector3 p2 = ShowPoint(i   1);  
  11.             Vector3 p3 = ShowPoint(i   2);  
  12.               
  13.             Handles.color = Color.gray;  
  14.             Handles.DrawLine(p0, p1);  
  15.             Handles.DrawLine(p2, p3);  
  16.               
  17.             Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);  
  18.             p0 = p3;  
  19.         }  
  20.         ShowDirections();  
  21.     }  



现在,我们可以看到所有的曲线,但是方向线只增加到第一个。这是因为BezierSpline的方法仍然只适用于第一个曲线。是时候改变

这种状况了。

为了覆盖整个样条,从0到1,我们需要首先求出我们在哪个曲线上。我们可以得到曲线的指数乘以t乘以曲线的数量然后丢弃分数。让我们

添加一个曲率属性来简化它。

[csharp] view plain copy
  1. public int CurveCount {  
  2.     get {  
  3.         return (points.Length - 1) / 3;  
  4.     }  
  5. }  

之后,我们可以将t减少到小数部分来得到曲线的内插值。为了得到实际的点,我们必须将曲线指数乘以3。

然而,当初始t = 1时,这将会失败。在这种情况下,我们可以把它设置成最后一条曲线。

[csharp] view plain copy
  1. public Vector3 GetPoint (float t) {  
  2.         int i;  
  3.         if (t >= 1f) {  
  4.             t = 1f;  
  5.             i = points.Length - 4;  
  6.         }  
  7.         else {  
  8.             t = Mathf.Clamp01(t) * CurveCount;  
  9.             i = (int)t;  
  10.             t -= i;  
  11.             i *= 3;  
  12.         }  
  13.         return transform.TransformPoint(Bezier.GetPoint(  
  14.             points[i], points[i   1], points[i   2], points[i   3], t));  
  15.     }  
  16.       
  17.     public Vector3 GetVelocity (float t) {  
  18.         int i;  
  19.         if (t >= 1f) {  
  20.             t = 1f;  
  21.             i = points.Length - 4;  
  22.         }  
  23.         else {  
  24.             t = Mathf.Clamp01(t) * CurveCount;  
  25.             i = (int)t;  
  26.             t -= i;  
  27.             i *= 3;  
  28.         }  
  29.         return transform.TransformPoint(Bezier.GetFirstDerivative(  
  30.             points[i], points[i   1], points[i   2], points[i   3], t)) - transform.position;  
  31.     }  

我们现在看到了整个样条的方向线,但是我们可以通过确保每个曲线段得到相同数量的线来改进可视化。幸运的是,很容易更改

BezierSplineInspector,显示方向,所以它使用BezierSpline,曲率决定画多少线。

[csharp] view plain copy
  1. private const int stepsPerCurve = 10;  
  2.       
  3.     private void ShowDirections () {  
  4.         Handles.color = Color.green;  
  5.         Vector3 point = spline.GetPoint(0f);  
  6.         Handles.DrawLine(point, point   spline.GetDirection(0f) * directionScale);  
  7.         int steps = stepsPerCurve * spline.CurveCount;  
  8.         for (int i = 1; i <= steps; i ) {  
  9.             point = spline.GetPoint(i / (float)steps);  
  10.             Handles.DrawLine(point, point   spline.GetDirection(i / (float)steps) * directionScale);  
  11.         }  
  12.     }  

更新ShowPoint,这样它显示一个按钮而不是一个位置句柄。这个按钮看起来像一个白色的点,当点击将会变成活动的点。

然后,如果point的索引与所选的索引匹配,那么我们只显示该位置句柄,这是我们在- 1中初始化的,所以默认情况下没有选择。

[csharp] view plain copy
  1. private const float handleSize = 0.04f;  
  2.     private const float pickSize = 0.06f;  
  3.       
  4.     private int selectedIndex = -1;  
  5.       
  6.     private Vector3 ShowPoint (int index) {  
  7.         Vector3 point = handleTransform.TransformPoint(spline.points[index]);  
  8.         Handles.color = Color.white;  
  9.         if (Handles.Button(point, handleRotation, handleSize, pickSize, Handles.DotCap)) {  
  10.             selectedIndex = index;  
  11.         }  
  12.         if (selectedIndex == index) {  
  13.             EditorGUI.BeginChangeCheck();  
  14.             point = Handles.DoPositionHandle(point, handleRotation);  
  15.             if (EditorGUI.EndChangeCheck()) {  
  16.                 Undo.RecordObject(spline, "Move Point");  
  17.                 EditorUtility.SetDirty(spline);  
  18.                 spline.points[index] = handleTransform.InverseTransformPoint(point);  
  19.             }  
  20.         }  
  21.         return point;  
  22.     }  


这是可行的,但很难让这些点有一个比较理想的尺寸,根据你所处的规模,他们可能要么太大,要么太小,如果我们能保持圆点的屏幕

尺寸不变就好了,就像位置句柄总是有相同的屏幕大小一样。我们可以通过在HandleUtility.GetHandleSize保理。这个方法为我们提供

了一个固定的屏幕尺寸,在世界范围内的任何一点。

[csharp] view plain copy
  1. float size = HandleUtility.GetHandleSize(point);  
  2. Handles.color = Color.white;  
  3. if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {  
  4.     selectedIndex = index;  
  5. }  



代码下载地址:链接:http://pan.baidu.com/s/1gfrJVrl  密码:ft1o 中的编号03的包。

http://blog.csdn.net/jxw167/article/details/77836509

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: