开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

蛮牛译馆

关注:595

当前位置:游戏蛮牛 技术专区 蛮牛译馆

查看: 2077|回复: 9

[Unity教程] Unity C#教程—绘制三维曲线和样条,设定自定义路径

[复制链接]  [移动端链接]
3偶尔光临
192/300
排名
21390
昨日变化
19

17

主题

24

帖子

192

积分

Rank: 3Rank: 3Rank: 3

UID
129264
好友
3
蛮牛币
324
威望
0
注册时间
2015-11-18
在线时间
75 小时
最后登录
2017-11-21

蛮牛译员

发表于 2016-11-20 17:22:06 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
本帖最后由 manew_JR 于 2016-11-21 10:05 编辑

这个教程会指导你从创建一条简单的线到编辑自己的Bezier 样条。你会学习:
创建一个自定义的编辑器
在屏幕中绘图
通过视图支持编辑
创建Bezier曲线并理解他们背后的数学
绘制曲线和他们的移动方向
通过合成曲线来创建Bezier样条
支持自由度,调整和镜像控制点
支持循环样条
沿着样条移动和放置物体
这个教程依赖于之前教程的基础。如果你完成Maze教程,会更好理解。
这个教程由Unity 4.5.2创建。可能不适用于老版本的unity。
线条

我们从创建线条组合来开始。需要两个点-po和p1-定义了一个线段,从第一个点到第二个点。
[C#] 纯文本查看 复制代码
using UnityEngine;

public class Line : MonoBehaviour {

        public Vector3 p0, p1;
}

一条简单的直线
当我们现在用线条组合来创建游戏主体并调整点,我们在屏幕中看不到任何东西。当我们的线被选择之后,会提供一些有用的视觉信息。我们为组件创建一个自定义的检查器
编辑器-相关的代码需要放置在编辑器文件夹,因此创建一个并放置新的LineInspector脚本在里面。
自查器需要扩展UnityEditor.Editor。我们不得不赋予UnityEditor.CustomEditor属性。让Unity知道应该用我们的类而不是默认的线条组合编辑器
typeof是做什么的?
编辑文件夹是必须的么?
[C#] 纯文本查看 复制代码
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Line))]
public class LineInspector : Editor {
}

一个空的编辑器不需要任何改变。我们需要添加OnScreenGUI方法,是一个特殊的unITY事件方法。我们能够用它在视图中为我们的组件绘制。

当OnSceneGUI被调用的时候,编辑类有个目标(target)变量,赋予将被画的物体。我们把这个变量给了线条然后使用Handles unity类来在我们的点中绘制一条线。
[C#] 纯文本查看 复制代码
private void OnSceneGUI () {
                Line line = target as Line;

                Handles.color = Color.white;
                Handles.DrawLine(line.p0, line.p1);
        }

展现一条直线
我们现在看到这条线,但不能把它的移位设置放到账户中移动,旋转,和缩放不能影响它们。因为世界坐标中的这些点的Handles操作是局部空间中的线。我们不得不转变这些点到世界坐标中。
[C#] 纯文本查看 复制代码
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);
        }

不平移和平移
除了展示线,我们也可以显示两个点的位置。为了做这个,我们也需要我们的移动旋转,因此我们可以正确地调整他们。
[C#] 纯文本查看 复制代码
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);
        }

尽管我们现在得到手柄,他们不遵循Unity的中心点旋转。幸运的是,我们可以用Tools.pivotRotation来决定现在的模式并设置我们的旋转。
[C#] 纯文本查看 复制代码
Quaternion handleRotation = Tools.pivotRotation == PivotRotation.Local ?
                        handleTransform.rotation : Quaternion.identity;

                             
局部和世界中心旋转
为了使手柄确实起效,我们需要调整他们的结果到线条。但是,因为手柄值在世界坐标中,我们需要用InverseTransformPoint方法转换他们到线的局部坐标。同时,当一点已经改变的话,我们也需要做这个。我们会用到EditorGUI.BeginChangeCheck和EditorGUI.EndChangeCheck。调用第一个方法之后,第二种方法告诉我们改变是否会发生。
[C#] 纯文本查看 复制代码
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);
                }

现在我们拖动点到场景视图中!

有两个另外的问题需要注意。首先,我们不能不做拖动操作。在我们做出改变之前,可以通过添加调用Undo.RecordObject来修复。其次,Unity不知道改变已经发生,因此比如不会询问用户来保存当停止的时候。记住调用EditorUtility.SetDirty
[C#] 纯文本查看 复制代码
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);
                }

曲线
是时候来改善曲线了。曲线像一条线,但不是直线。具体来说,我们将创建一天Bezier曲线。
贝塞尔曲线由一系列点来定义。始于第一个点,终于最后一个点,但不需要经过中间点。相反,这些点拉曲线使其变成一条直线。


创建一条新的贝塞尔曲线部分并给予一组点。同样给予一个Reset方法并用三个点初始化。这个方法作为特殊的untiy方法起作用,当组件创建或重置时,通过调用编辑器来起作用。
[C#] 纯文本查看 复制代码
using UnityEngine;

public class BezierCurve : MonoBehaviour {

        public Vector3[] points;

        public void Reset () {
                points = new Vector3[] {
                        new Vector3(1f, 0f, 0f),
                        new Vector3(2f, 0f, 0f),
                        new Vector3(3f, 0f, 0f)
                };
        }
}

我们也创建曲线的检查器,基于LineInspector。为了减少代码重复,我们移除代码表明一个点到独立的ShowPoint方法以至于我们能用索引来调用。我们把curve,handleTransform,及handleRotation转变成类变量,以至于我们不需要传递并showPoint。


因为它是一个新的脚本,我标注了不同点就像我们修改LineInspector。
[C#] 纯文本查看 复制代码
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(BezierCurve))]
public class BezierCurveInspector : Editor {

        private BezierCurve curve;
        private Transform handleTransform;
        private Quaternion handleRotation;

        private void OnSceneGUI () {
                curve = target as BezierCurve;
                handleTransform = curve.transform;
                handleRotation = Tools.pivotRotation == PivotRotation.Local ?
                        handleTransform.rotation : Quaternion.identity;

                Vector3 p0 = ShowPoint(0);
                Vector3 p1 = ShowPoint(1);
                Vector3 p2 = ShowPoint(2);

                Handles.color = Color.white;
                Handles.DrawLine(p0, p1);
                Handles.DrawLine(p1, p2);
        }

        private Vector3 ShowPoint (int index) {
                Vector3 point = handleTransform.TransformPoint(curve.points[index]);
                EditorGUI.BeginChangeCheck();
                point = Handles.DoPositionHandle(point, handleRotation);
                if (EditorGUI.EndChangeCheck()) {
                        Undo.RecordObject(curve, "Move Point");
                        EditorUtility.SetDirty(curve);
                        curve.points[index] = handleTransform.InverseTransformPoint(point);
                }
                return point;
        }
}

3点曲线
贝塞尔曲线是参数化的。如果你给它值-通常命名为t-值在0到1之间,你在曲线上选择一点。由于t从0到1不断增长,你从曲线上的第一个点移动到最后一个。

为了在场景中展现曲线,我们可以通过绘制直线来估计曲线连续的步子。我们用一个简单的循环来做这个,假设我们的曲线有个GetPoint方法,我们可以在点之间绘制直线,但改变他们的颜色为灰色。
当然,在点之间线性插入完全忽视了中间点。因此,我们怎么包含中间点?答案是包含多次。首先,在第一个点和中间点中线性插入,同时在中间点和最终点中线性插入。给我们两个新的点。两点间的线性插入给予我们曲线上的最终点。
[C#] 纯文本查看 复制代码
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
                return Vector3.Lerp(Vector3.Lerp(p0, p1, t), Vector3.Lerp(p1, p2, t), t);
        }

贝塞尔曲线是参数化的。如果你给它值-通常命名为t-值在0到1之间,你在曲线上选择一点。由于t从0到1不断增长,你从曲线上的第一个点移动到最后一个。
为了在场景中展现曲线,我们可以通过绘制直线来估计曲线连续的步子。我们用一个简单的循环来做这个,假设我们的曲线有个GetPoint方法,我们可以在点之间绘制直线,但改变他们的颜色为灰色。
[C#] 纯文本查看 复制代码
private const[mw_shl_code=csharp,true]private const int lineSteps = 10;
                        
        private void OnSceneGUI () {
                curve = target as BezierCurve;
                handleTransform = curve.transform;
                handleRotation = Tools.pivotRotation == PivotRotation.Local ?
                        handleTransform.rotation : Quaternion.identity;
 
                Vector3 p0 = ShowPoint(0);
                Vector3 p1 = ShowPoint(1);
                Vector3 p2 = ShowPoint(2);
 
                Handles.color = Color.gray;
                Handles.DrawLine(p0, p1);
                Handles.DrawLine(p1, p2);
 
                Handles.color = Color.white;
                Vector3 lineStart = curve.GetPoint(0f);
                for (int i = 1; i <= lineSteps; i++) {
                        Vector3 lineEnd = curve.GetPoint(i / (float)lineSteps);
                        Handles.DrawLine(lineStart, lineEnd);
                        lineStart = lineEnd;
                }
        }

现在我们不得不添加GetPoint方法到贝塞尔曲线。相反,不会编译。我们做个假设,这次有一个实用的贝塞尔类,为任意的连续的点做运算。我们给予我们的点并转变结果到世界坐标。
[C#] 纯文本查看 复制代码
public Vector3 GetPoint (float t) {
                return transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], t));
        }
因此我们用必须的方法添加一个静态的贝塞尔类。现在,我们忽视中间点并简单地在第一个和第二个点之间线性插入。
[C#] 纯文本查看 复制代码
using UnityEngine;
 
public static class Bezier {
 
        public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
                return Vector3.Lerp(p0, p2, t);
        }
}

贝塞尔库和直线插值
当然,在点之间线性插入完全忽视了中间点。因此,我们怎么包含中间点?答案是包含多次。首先,在第一个点和中间点中线性插入,同时在中间点和最终点中线性插入。给我们两个新的点。两点间的线性插入给予我们曲线上的最终点。
[C#] 纯文本查看 复制代码
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
                return Vector3.Lerp(Vector3.Lerp(p0, p1, t), Vector3.Lerp(p1, p2, t), t);
        }

Quadratic 贝塞尔曲线
这种曲线称为二次项贝塞尔曲线,因为进行了多项式数学运算
线性曲线可以写成 B(t)= (1 - t) P0 + t P1
展开可以得到B(t) = (1 - t)((1 - t) P0 + t P1) + t ((1 - t)P1 + t P2). 这是线性曲线有P0和P1,被两个新的线性曲线取代。可以被写成更紧凑的 B(t) = (1 - t)2 P0 + 2 (1 - t) t P1 + t2 P2

因此我们可以用二次项公示而不是三次调用Vector3.Lerp。
[C#] 纯文本查看 复制代码
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
                t = Mathf.Clamp01(t);
                float oneMinusT = 1f - t;
                return
                        oneMinusT * oneMinusT * p0 +
                        2f * oneMinusT * t * p1 +
                        t * t * p2;
        }

既然我们有多项式,我们也可描述它的导数。二次项式Bezier曲线的第一次求导得到B'(t) = 2 (1 - t) (P1 - P0)+ 2 t (P2 - P1).我们在程序中加上它。
[C#] 纯文本查看 复制代码
public static Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
                return
                        2f * (1f - t) * (p1 - p0) +
                        2f * t * (p2 - p1);
        }

这个公式产生曲线的切线,能够被解释为我们沿着曲线运动的速度。现在我们添加一个GetVelocity方法到贝塞尔曲线。

因为产生速度向量而不是点,不会被曲线的位置所影响,移动之后我们减去移动的位置。
[C#] 纯文本查看 复制代码
public Vector3 GetVelocity (float t) {
                return transform.TransformPoint(Bezier.GetFirstDerivative(points[0], points[1], points[2], t)) -
                        transform.position;
        }

现在我们可以可视化曲线速度BezierCurveInspector的OnSceneGUI方法。
[C#] 纯文本查看 复制代码
Vector3 lineStart = curve.GetPoint(0f);
                Handles.color = Color.green;
                Handles.DrawLine(lineStart, lineStart + curve.GetVelocity(0f));
                for (int i = 1; i <= lineSteps; i++) {
                        Vector3 lineEnd = curve.GetPoint(i / (float)lineSteps);
                        Handles.color = Color.white;
                        Handles.DrawLine(lineStart, lineEnd);
                        Handles.color = Color.green;
                        Handles.DrawLine(lineEnd, lineEnd + curve.GetVelocity(i / (float)lineSteps));
                        lineStart = lineEnd;
                }

速度展示
我们可以清楚地看到曲线速度是怎么改变的,但这些长长的线被聚集在视图中。相比展现速度,我们可以足够展现移动的方向。
[C#] 纯文本查看 复制代码
Vector3 lineStart = curve.GetPoint(0f);
                Handles.color = Color.green;
                Handles.DrawLine(lineStart, lineStart + curve.GetDirection(0f));
                for (int i = 1; i <= lineSteps; i++) {
                        Vector3 lineEnd = curve.GetPoint(i / (float)lineSteps);
                        Handles.color = Color.white;
                        Handles.DrawLine(lineStart, lineEnd);
                        Handles.color = Color.green;
                        Handles.DrawLine(lineEnd, lineEnd + curve.GetDirection(i / (float)lineSteps));
                        lineStart = lineEnd;
                }

需要我们添加GetDirection到BezierCurve,简单常量化速度。
[C#] 纯文本查看 复制代码
public Vector3 GetDirection (float t) {
                return GetVelocity(t).normalized;
        }

方向展现
我们更进一步,为三次曲线添加新方法到Bezier.它会起作用像二次项视觉,除了需要一个第四个点,公式更进一步,导致六个线性插入集合在一起。统一的公式变成 B(t) = (1 - t)3 P0 + 3 (1 - t)2 t P1 + 3 (1 - t) t2 P2 + t3 P3,第一个衍生公式B'(t) = 3 (1 - t)2 (P1 - P0) + 6 (1 - t) t (P2 - P1) + 3 t2 (P3 -P2).
[C#] 纯文本查看 复制代码
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
                t = Mathf.Clamp01(t);
                float oneMinusT = 1f - t;
                return
                        oneMinusT * oneMinusT * oneMinusT * p0 +
                        3f * oneMinusT * oneMinusT * t * p1 +
                        3f * oneMinusT * t * t * p2 +
                        t * t * t * p3;
        }
        
        public static Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
                t = Mathf.Clamp01(t);
                float oneMinusT = 1f - t;
                return
                        3f * oneMinusT * oneMinusT * (p1 - p0) +
                        6f * oneMinusT * t * (p2 - p1) +
                        3f * t * t * (p3 - p2);
        }

我们可以通过考虑添加一个额外点点来升级BezierCurve,从二次多项式到三次多项式。确保添加第四个点到数组或手动或通过重置组成成分。
[C#] 纯文本查看 复制代码
public Vector3 GetPoint (float t) {
                return transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], points[3], t));
        }
        
        public Vector3 GetVelocity (float t) {
                return transform.TransformPoint(
                        Bezier.GetFirstDerivative(points[0], points[1], points[2], points[3], t)) - transform.position;
        }
        
        public void Reset () {
                points = new Vector3[] {
                        new Vector3(1f, 0f, 0f),
                        new Vector3(2f, 0f, 0f),
                        new Vector3(3f, 0f, 0f),
                        new Vector3(4f, 0f, 0f)
                };
        }

BezierCurveInspector现在需要升级,这样能展现第四个点。
[C#] 纯文本查看 复制代码
Vector3 p0 = ShowPoint(0);
                Vector3 p1 = ShowPoint(1);
                Vector3 p2 = ShowPoint(2);
                Vector3 p3 = ShowPoint(3);
                
                Handles.color = Color.gray;
                Handles.DrawLine(p0, p1);
                Handles.DrawLine(p2, p3);

Cubic 贝塞尔曲线
现在显然地是我们用直线部分来画曲线。我们通过提高步骤的数量来提高可视化质量。我们也能用交互的方式来得到准确得像素水平。但我们也能用Unity的Handles.DrawBezier方法,画漂亮的三次多项式贝塞尔曲线。我们看看他们自己方法的指向并缩放他们到占用很少的空间。
[C#] 纯文本查看 复制代码
private const float directionScale = 0.5f;
        
        private void OnSceneGUI () {
                curve = target as BezierCurve;
                handleTransform = curve.transform;
                handleRotation = Tools.pivotRotation == PivotRotation.Local ?
                        handleTransform.rotation : Quaternion.identity;
                
                Vector3 p0 = ShowPoint(0);
                Vector3 p1 = ShowPoint(1);
                Vector3 p2 = ShowPoint(2);
                Vector3 p3 = ShowPoint(3);
                
                Handles.color = Color.gray;
                Handles.DrawLine(p0, p1);
                Handles.DrawLine(p2, p3);
                
                ShowDirections();
                Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);
        }

        private void ShowDirections () {
                Handles.color = Color.green;
                Vector3 point = curve.GetPoint(0f);
                Handles.DrawLine(point, point + curve.GetDirection(0f) * directionScale);
                for (int i = 1; i <= lineSteps; i++) {
                        point = curve.GetPoint(i / (float)lineSteps);
                        Handles.DrawLine(point, point + curve.GetDirection(i / (float)lineSteps) * directionScale);
                }
        }

用手柄画贝塞尔曲线并缩放方向线
样条曲线

单条曲线是漂亮的,但为了创建更复杂的路径,我们需要连接多条曲线。这样的构造就是样条曲线。我们通过复制贝塞尔曲线的代码来创建一个,改变模式到贝塞尔样条曲线。
[C#] 纯文本查看 复制代码
using UnityEngine;

public class BezierSpline : MonoBehaviour {

        public Vector3[] points;
        
        public Vector3 GetPoint (float t) {
                return transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], points[3], t));
        }
        
        public Vector3 GetVelocity (float t) {
                return transform.TransformPoint(
                        Bezier.GetFirstDerivative(points[0], points[1], points[2], points[3], t)) - transform.position;
        }
        
        public Vector3 GetDirection (float t) {
                return GetVelocity(t).normalized;
        }
        
        public void Reset () {
                points = new Vector3[] {
                        new Vector3(1f, 0f, 0f),
                        new Vector3(2f, 0f, 0f),
                        new Vector3(3f, 0f, 0f),
                        new Vector3(4f, 0f, 0f)
                };
        }
}

我们也为它创建了编辑器,通过复制和调整BezierCurveInspector,我们能创建一个样条物体并编辑,就像一条曲线。
[C#] 纯文本查看 复制代码
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(BezierSpline))]
public class BezierSplineInspector : Editor {

        private const int lineSteps = 10;
        private const float directionScale = 0.5f;

        private BezierSpline spline;
        private Transform handleTransform;
        private Quaternion handleRotation;

        private void OnSceneGUI () {
                spline = target as BezierSpline;
                handleTransform = spline.transform;
                handleRotation = Tools.pivotRotation == PivotRotation.Local ?
                        handleTransform.rotation : Quaternion.identity;
                
                Vector3 p0 = ShowPoint(0);
                Vector3 p1 = ShowPoint(1);
                Vector3 p2 = ShowPoint(2);
                Vector3 p3 = ShowPoint(3);
                
                Handles.color = Color.gray;
                Handles.DrawLine(p0, p1);
                Handles.DrawLine(p2, p3);
                
                ShowDirections();
                Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);
        }

        private void ShowDirections () {
                Handles.color = Color.green;
                Vector3 point = spline.GetPoint(0f);
                Handles.DrawLine(point, point + spline.GetDirection(0f) * directionScale);
                for (int i = 1; i <= lineSteps; i++) {
                        point = spline.GetPoint(i / (float)lineSteps);
                        Handles.DrawLine(point, point + spline.GetDirection(i / (float)lineSteps) * directionScale);
                }
        }

        private Vector3 ShowPoint (int index) {
                Vector3 point = handleTransform.TransformPoint(spline.points[index]);
                EditorGUI.BeginChangeCheck();
                point = Handles.DoPositionHandle(point, handleRotation);
                if (EditorGUI.EndChangeCheck()) {
                        Undo.RecordObject(spline, "Move Point");
                        EditorUtility.SetDirty(spline);
                        spline.points[index] = handleTransform.InverseTransformPoint(point);
                }
                return point;
        }
}

新的样条类型
我们添加方法到BezierSpline并添加另一条曲线到样条曲线。因为我们想要样条曲线是连续的,先前曲线的最后一点与下一条曲线的第一个点是一样的。因此每天曲线添加三个或多个点。
[C#] 纯文本查看 复制代码
public void AddCurve () {
                Vector3 point = points[points.Length - 1];
                Array.Resize(ref points, points.Length + 3);
                point.x += 1f;
                points[points.Length - 3] = point;
                point.x += 1f;
                points[points.Length - 2] = point;
                point.x += 1f;
                points[points.Length - 1] = point;
        }

我们用Array.Resize方法来创建一个更大的数组来存储新的点。在System域名中,因此我们声明我们在脚本顶部使用它。
[C#] 纯文本查看 复制代码
using UnityEngine;
using System;

为了真正能够添加曲线,我们不得不为样条曲线的检测器添加一个按钮。我们可以自定义检测器,unity通过重写BezierSplineInspector的OnInspectorGUI的方法使用我们的组件。注意这是unity特殊的方法,依赖于继承。

为了绘制默认的检查器,我们调用DrawDefaultInspector方法。我们使用GUILayout来绘制按钮,当点击添加曲线的时候。
[C#] 纯文本查看 复制代码
public override void OnInspectorGUI () {
                DrawDefaultInspector();
                spline = target as BezierSpline;
                if (GUILayout.Button("Add Curve")) {
                        Undo.RecordObject(spline, "Add Curve");
                        spline.AddCurve();
                        EditorUtility.SetDirty(spline);
                }
        }

添加一条曲线
当然我们仍然仅仅看到第一条曲线,因为我们调整BezierSplineInspector因此循环于整个曲线。
[C#] 纯文本查看 复制代码
private void OnSceneGUI () {
                spline = target as BezierSpline;
                handleTransform = spline.transform;
                handleRotation = Tools.pivotRotation == PivotRotation.Local ?
                        handleTransform.rotation : Quaternion.identity;
                
                Vector3 p0 = ShowPoint(0);
                for (int i = 1; i < spline.points.Length; i += 3) {
                        Vector3 p1 = ShowPoint(i);
                        Vector3 p2 = ShowPoint(i + 1);
                        Vector3 p3 = ShowPoint(i + 2);
                        
                        Handles.color = Color.gray;
                        Handles.DrawLine(p0, p1);
                        Handles.DrawLine(p2, p3);
                        
                        Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);
                        p0 = p3;
                }
                ShowDirections();
        }

整个样条曲线
现在我们看到所有的曲线,但是直线仅仅添加在第一条曲线上。因为贝塞尔样条曲线方法仍然作用于第一条曲线。是时候改变它了。

为了覆盖整个样条曲线,用t从0到1的一个数,我们首先需要愤青我们在哪条曲线。我们通过曲线的数量乘以t来得到曲线的索引,然后丢掉分式。我们添加CurveCount属性来使其变得更容易。
[C#] 纯文本查看 复制代码
public int CurveCount {
                get {
                        return (points.Length - 1) / 3;
                }
        }

然后我们可以减少t到很小来得到曲线的插值。为了得到真实的点,我们不得不用曲线的索引乘以3.

但是,当t等于1的时候会失败。这个方案,我们放置它到最后一条曲线。
[C#] 纯文本查看 复制代码
public Vector3 GetPoint (float t) {
                int i;
                if (t >= 1f) {
                        t = 1f;
                        i = points.Length - 4;
                }
                else {
                        t = Mathf.Clamp01(t) * CurveCount;
                        i = (int)t;
                        t -= i;
                        i *= 3;
                }
                return transform.TransformPoint(Bezier.GetPoint(
                        points, points[i + 1], points[i + 2], points[i + 3], t));
        }
        
        public Vector3 GetVelocity (float t) {
                int i;
                if (t >= 1f) {
                        t = 1f;
                        i = points.Length - 4;
                }
                else {
                        t = Mathf.Clamp01(t) * CurveCount;
                        i = (int)t;
                        t -= i;
                        i *= 3;
                }
                return transform.TransformPoint(Bezier.GetFirstDerivative(
                        points, points[i + 1], points[i + 2], points[i + 3], t)) - transform.position;
        }

我们现在透过整个样条曲线来看直线,但我们可以提高可视化通过确保每条曲线得到相同数量的直线。幸运的是,很容易改变BezierSplineInspector.ShowDirections,用BezierSpline.CurveCount来决定绘制几条线。
[C#] 纯文本查看 复制代码
private const int stepsPerCurve = 10;
        
        private void ShowDirections () {
                Handles.color = Color.green;
                Vector3 point = spline.GetPoint(0f);
                Handles.DrawLine(point, point + spline.GetDirection(0f) * directionScale);
                int steps = stepsPerCurve * spline.CurveCount;
                for (int i = 1; i <= steps; i++) {
                        point = spline.GetPoint(i / (float)steps);
                        Handles.DrawLine(point, point + spline.GetDirection(i / (float)steps) * directionScale);
                }
        }

沿着整条样条曲线的方向
这些转变控制太多了。我们仅仅展现活动点的控制。其他点用点足够

我们更新ShowPoint。它展现按钮而不是位置控制。这个按钮看起来像白点,点击的时候会转成活动点。我们仅仅展现位置控制如果点的索引匹配选择的索引,我们初始化为-1,则默认什么都没有选中。
[C#] 纯文本查看 复制代码
private const float handleSize = 0.04f;
        private const float pickSize = 0.06f;
        
        private int selectedIndex = -1;
        
        private Vector3 ShowPoint (int index) {
                Vector3 point = handleTransform.TransformPoint(spline.points[index]);
                Handles.color = Color.white;
                if (Handles.Button(point, handleRotation, handleSize, pickSize, Handles.DotCap)) {
                        selectedIndex = index;
                }
                if (selectedIndex == index) {
                        EditorGUI.BeginChangeCheck();
                        point = Handles.DoPositionHandle(point, handleRotation);
                        if (EditorGUI.EndChangeCheck()) {
                                Undo.RecordObject(spline, "Move Point");
                                EditorUtility.SetDirty(spline);
                                spline.points[index] = handleTransform.InverseTransformPoint(point);
                        }
                }
                return point;
        }

显示点
起作用了,但获得点的大小很粗糙。基于你操作的点的大小,他们以太大或太小的方式来结束。如果我们能保持点的屏幕大小修复会更好,就像位置控制总是有相同的屏幕尺寸。我们可以通过HandleUtility来做。GetHandleSize。这个方法给予我们世界坐标中的任何一个点的一个固定的屏幕大小。
[C#] 纯文本查看 复制代码
float size = HandleUtility.GetHandleSize(point);
                Handles.color = Color.white;
                if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
                        selectedIndex = index;
                }

固定大小的点
控制点约束
尽管我们的样条曲线是连续的,改变了曲线部分的指向。这些指向的突然改变和速度是可能的因为两条曲线间共享的控制点有两种不同的速率。每条曲线一个。

如果我们想速率是相同的,我们必须确保两个控制点来定义他们-前端曲线的第三个和下一段曲线的第二个--围绕共享的一个点镜像每一个。确保联合第一个和第二个派生是连续的。
或者,我们调整他们单让他们与共享点的距离不同。将导致速率的突然改变,但仍会保持方向性连续。这个案例中,第一个派生的组合部分是连续的,但第二个不是。
最灵活的方法是决定每个曲线的边界,约束可以实行,因此我们这么做。当然,一旦我们有了这些约束,我们不能让任何人直接编辑贝塞尔样条曲线的点。因此我们让数组private并且提供间接的通道来到达它。确保让unity知道我们仍然像序列化我们的点,相反他们不会被保存。
[C#] 纯文本查看 复制代码
[SerializeField]
        private Vector3[] points;

        public int ControlPointCount {
                get {
                        return points.Length;
                }
        }

        public Vector3 GetControlPoint (int index) {
                return points[index];
        }

        public void SetControlPoint (int index, Vector3 point) {
                points[index] = point;
        }

现在BezierSplineInspector必须用新的方法而不是直接地到达点数组。
[C#] 纯文本查看 复制代码
private void OnSceneGUI () {
                spline = target as BezierSpline;
                handleTransform = spline.transform;
                handleRotation = Tools.pivotRotation == PivotRotation.Local ?
                        handleTransform.rotation : Quaternion.identity;
                
                Vector3 p0 = ShowPoint(0);
                for (int i = 1; i < spline.ControlPointCount; i += 3) {
                        Vector3 p1 = ShowPoint(i);
                        Vector3 p2 = ShowPoint(i + 1);
                        Vector3 p3 = ShowPoint(i + 2);
                        
                        Handles.color = Color.gray;
                        Handles.DrawLine(p0, p1);
                        Handles.DrawLine(p2, p3);
                        
                        Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);
                        p0 = p3;
                }
                ShowDirections();
        }
        
        private Vector3 ShowPoint (int index) {
                Vector3 point = handleTransform.TransformPoint(spline.GetControlPoint(index));
                float size = HandleUtility.GetHandleSize(point);
                Handles.color = Color.white;
                if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
                        selectedIndex = index;
                }
                if (selectedIndex == index) {
                        EditorGUI.BeginChangeCheck();
                        point = Handles.DoPositionHandle(point, handleRotation);
                        if (EditorGUI.EndChangeCheck()) {
                                Undo.RecordObject(spline, "Move Point");
                                EditorUtility.SetDirty(spline);
                                spline.SetControlPoint(index, handleTransform.InverseTransformPoint(point));
                        }
                }
                return point;
        }

当我们。。。。我们也不再想允许直接的通道到数组在检查器中,因此移除DrawDefaultInspector调用。通过打印来允许改变,我们为选中的点来展现一个矢量域。
[C#] 纯文本查看 复制代码
public override void OnInspectorGUI () {
                spline = target as BezierSpline;
                if (selectedIndex >= 0 && selectedIndex < spline.ControlPointCount) {
                        DrawSelectedPointInspector();
                }
                if (GUILayout.Button("Add Curve")) {
                        Undo.RecordObject(spline, "Add Curve");
                        spline.AddCurve();
                        EditorUtility.SetDirty(spline);
                }
        }

        private void DrawSelectedPointInspector() {
                GUILayout.Label("Selected Point");
                EditorGUI.BeginChangeCheck();
                Vector3 point = EditorGUILayout.Vector3Field("Position", spline.GetControlPoint(selectedIndex));
                if (EditorGUI.EndChangeCheck()) {
                        Undo.RecordObject(spline, "Move Point");
                        EditorUtility.SetDirty(spline);
                        spline.SetControlPoint(selectedIndex, point);
                }
        }

不幸的是,事实证明检查器不能刷新当我们在视图中选择一个点时。我们可以通过调用SetDirty来修复这个。对于样条曲线来说,但这是不对的因为样条曲线不能改变。幸运的是,相反我们可以发表一个重新绘制的请求。
[C#] 纯文本查看 复制代码
private Vector3 ShowPoint (int index) {
                Vector3 point = handleTransform.TransformPoint(spline.GetControlPoint(index));
                float size = HandleUtility.GetHandleSize(point);
                Handles.color = Color.white;
                if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
                        selectedIndex = index;
                        Repaint();
                }
                if (selectedIndex == index) {
                        EditorGUI.BeginChangeCheck();
                        point = Handles.DoPositionHandle(point, handleRotation);
                        if (EditorGUI.EndChangeCheck()) {
                                Undo.RecordObject(spline, "Move Point");
                                EditorUtility.SetDirty(spline);
                                spline.SetControlPoint(index, handleTransform.InverseTransformPoint(point));
                        }
                }
                return point;
        }

仅仅选择点
我们定义个列举类型来描绘我们的三个模式。
[C#] 纯文本查看 复制代码
public enum BezierControlPointMode {
        Free,
        Aligned,
        Mirrored
}

现在我们添加这些模式到贝塞尔样条曲线。我们仅仅需要在曲线中存储模式,因此我们把它们放置到与曲线数量一致的数组中。你会需要重新设置样条曲线或创建一个新的来确保你有个合适长度的数组。
[C#] 纯文本查看 复制代码
[SerializeField]
        private BezierControlPointMode[] modes;
        
        public void AddCurve () {
                Vector3 point = points[points.Length - 1];
                Array.Resize(ref points, points.Length + 3);
                point.x += 1f;
                points[points.Length - 3] = point;
                point.x += 1f;
                points[points.Length - 2] = point;
                point.x += 1f;
                points[points.Length - 1] = point;

                Array.Resize(ref modes, modes.Length + 1);
                modes[modes.Length - 1] = modes[modes.Length - 2];
        }
        
        public void Reset () {
                points = new Vector3[] {
                        new Vector3(1f, 0f, 0f),
                        new Vector3(2f, 0f, 0f),
                        new Vector3(3f, 0f, 0f),
                        new Vector3(4f, 0f, 0f)
                };
                modes = new BezierControlPointMode[] {
                        BezierControlPointMode.Free,
                        BezierControlPointMode.Free
                };
        }

当我们在曲线中存储模式,如果我们能够得到和设置每个控制点的模式会很方便。因此我们需要转换点的索引成模式索引因为在现实中,点共享模式。例如,点的索引序列0,1,2,3,4,5,6与模式索引序列0,0,1,1,1,2,2对应。因此我们需要增加点然后分成3份。
[C#] 纯文本查看 复制代码
public BezierControlPointMode GetControlPointMode (int index) {
                return modes[(index + 1) / 3];
        }

        public void SetControlPointMode (int index, BezierControlPointMode mode) {
                modes[(index + 1) / 3] = mode;
        }

现在BezierSplineInspecor可以允许我们改变已选择的点的模式。你注意到改变一个点的模式也出现改变连接它的点的模式。
[C#] 纯文本查看 复制代码
private void DrawSelectedPointInspector() {
                GUILayout.Label("Selected Point");
                EditorGUI.BeginChangeCheck();
                Vector3 point = EditorGUILayout.Vector3Field("Position", spline.GetControlPoint(selectedIndex));
                if (EditorGUI.EndChangeCheck()) {
                        Undo.RecordObject(spline, "Move Point");
                        EditorUtility.SetDirty(spline);
                        spline.SetControlPoint(selectedIndex, point);
                }
                EditorGUI.BeginChangeCheck();
                BezierControlPointMode mode = (BezierControlPointMode)
                        EditorGUILayout.EnumPopup("Mode", spline.GetControlPointMode(selectedIndex));
                if (EditorGUI.EndChangeCheck()) {
                        Undo.RecordObject(spline, "Change Point Mode");
                        spline.SetControlPointMode(selectedIndex, mode);
                        EditorUtility.SetDirty(spline);
                }
        }

可控制点模式
如果我们可以得到场景中的结点类型的可视化反馈会很有用。我们可以轻易地通过色彩化点来添加这个。我用白色,黄色来调整,青蓝色是镜像的。
[C#] 纯文本查看 复制代码
private static Color[] modeColors = {
                Color.white,
                Color.yellow,
                Color.cyan
        };
        
        private Vector3 ShowPoint (int index) {
                Vector3 point = handleTransform.TransformPoint(spline.GetControlPoint(index));
                float size = HandleUtility.GetHandleSize(point);
                Handles.color = modeColors[(int)spline.GetControlPointMode(index)];
                if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
                        selectedIndex = index;
                        Repaint();
                }
                if (selectedIndex == index) {
                        EditorGUI.BeginChangeCheck();
                        point = Handles.DoPositionHandle(point, handleRotation);
                        if (EditorGUI.EndChangeCheck()) {
                                Undo.RecordObject(spline, "Move Point");
                                EditorUtility.SetDirty(spline);
                                spline.SetControlPoint(index, handleTransform.InverseTransformPoint(point));
                        }
                }
                return point;
        }

带有了彩色控制点
目前我们彩色化点。是时候加强约束了。我们添加一个新的方法到BezierSpline来这么做并调用它,当点被移动或模式被改变。它带走了点的索引并通过取回相关的模式来开始。
[C#] 纯文本查看 复制代码
        public void SetControlPoint (int index, Vector3 point) {
                points[index] = point;
                EnforceMode(index);
        }
        
        public void SetControlPointMode (int index, BezierControlPointMode mode) {
                modes[(index + 1) / 3] = mode;
                EnforceMode(index);
        }

        private void EnforceMode (int index) {
                int modeIndex = (index + 1) / 3;
        }

我们应当检测我们是否确实没有加强任何事。当模式被设置为免费的,这是个案例或者当我们在曲线的末点。在这些案例中,我们可以不用做任何事就返回。
[C#] 纯文本查看 复制代码
private void EnforceMode (int index) {
                int modeIndex = (index + 1) / 3;
                BezierControlPointMode mode = modes[modeIndex];
                if (mode == BezierControlPointMode.Free || modeIndex == 0 || modeIndex == modes.Length - 1) {
                        return;
                }
        }

现在我们需要调整哪个点?当我们改变一个点的模式,可能是曲线间的一个点或者邻点的一个点。当我们有选中的中间点时,我们能修复之前的点并增强对立面的点的约束。如果我们已经选择了其他点的一个点,我们可以保持修复一个点并调整它的对立面。我们选择点的方式总是保持在它的地方。因此我们定义这些点的指标。
[C#] 纯文本查看 复制代码
if (mode == BezierControlPointMode.Free || modeIndex == 0 || modeIndex == modes.Length - 1) {
                        return;
                }
                
                int middleIndex = modeIndex * 3;
                int fixedIndex, enforcedIndex;
                if (index <= middleIndex) {
                        fixedIndex = middleIndex - 1;
                        enforcedIndex = middleIndex + 1;
                }
                else {
                        fixedIndex = middleIndex + 1;
                        enforcedIndex = middleIndex - 1;
                }

我们首先考虑下镜像的例子。对中心点进行惊喜那个。我们不得不把矢量从中间点移到修复点-(固定的中心点)-对它进行反转。这是强加的正切线,并把它加入到中心,给予我们加强的点。
[C#] 纯文本查看 复制代码
if (index <= middleIndex) {
                        fixedIndex = middleIndex - 1;
                        enforcedIndex = middleIndex + 1;
                }
                else {
                        fixedIndex = middleIndex + 1;
                        enforcedIndex = middleIndex - 1;
                }

                Vector3 middle = points[middleIndex];
                Vector3 enforcedTangent = middle - points[fixedIndex];
                points[enforcedIndex] = middle + enforcedTangent;

对于调整模式,我们不得不确定新的正切线与旧的有相同的长度。因此我们标准化并乘以中点和旧的加强点之间的距离。
[C#] 纯文本查看 复制代码
Vector3 enforcedTangent = middle - points[fixedIndex];
                if (mode == BezierControlPointMode.Aligned) {
                        enforcedTangent = enforcedTangent.normalized * Vector3.Distance(middle, points[enforcedIndex]);
                }
                points[enforcedIndex] = middle + enforcedTangent;


强制约束
从现在开始,不论你移动一个点或者改变点的模式,约束会被执行。但当移动中间点时,以前的点总是保持固定,下一个点总是被执行。这挺不错,但是感觉如果其他点随着中间点移动。那么我们调整SetControlPoint,使得它们能一起移动。
[C#] 纯文本查看 复制代码
public void SetControlPoint (int index, Vector3 point) {
                if (index % 3 == 0) {
                        Vector3 delta = point - points[index];
                        if (index > 0) {
                                points[index - 1] += delta;
                        }
                        if (index + 1 < points.Length) {
                                points[index + 1] += delta;
                        }
                }
                points[index] = point;
                EnforceMode(index);
        }

为了封装,我们也确定约束被执行当我们添加一条曲线时。我们可以简单通过在点的地方调用EnforceMode,新的曲线会被添加。
[C#] 纯文本查看 复制代码
public void AddCurve () {
                Vector3 point = points[points.Length - 1];
                Array.Resize(ref points, points.Length + 3);
                point.x += 1f;
                points[points.Length - 3] = point;
                point.x += 1f;
                points[points.Length - 2] = point;
                point.x += 1f;
                points[points.Length - 1] = point;

                Array.Resize(ref modes, modes.Length + 1);
                modes[modes.Length - 1] = modes[modes.Length - 2];
                EnforceMode(points.Length - 4);
        }

还有另一个约束我们需要添加。通过执行第一个和最后一个点共享同样的位置,我们可以把我们的样条曲线转变成线圈。当然,我们也不得不考虑模式。
因此我们添加线圈属性到贝塞尔样条曲线。不论何时设置为属性为真,我们确定最后点的模式匹配并调用SetPosition,相信会更关心位置和模式约束。
[C#] 纯文本查看 复制代码
[SerializeField]
        private bool loop;

        public bool Loop {
                get {
                        return loop;
                }
                set {
                        loop = value;
                        if (value == true) {
                                modes[modes.Length - 1] = modes[0];
                                SetControlPoint(0, points[0]);
                        }
                }
        }

现在我们添加线圈属性到BezierSplineInspector。
[C#] 纯文本查看 复制代码
public override void OnInspectorGUI () {
                spline = target as BezierSpline;
                EditorGUI.BeginChangeCheck();
                bool loop = EditorGUILayout.Toggle("Loop", spline.Loop);
                if (EditorGUI.EndChangeCheck()) {
                        Undo.RecordObject(spline, "Toggle Loop");
                        EditorUtility.SetDirty(spline);
                        spline.Loop = loop;
                }
                if (selectedIndex >= 0 && selectedIndex < spline.ControlPointCount) {
                        DrawSelectedPointInspector();
                }
                if (GUILayout.Button("Add Curve")) {
                        Undo.RecordObject(spline, "Add Curve");
                        spline.AddCurve();
                        EditorUtility.SetDirty(spline);
                }
        }

可选择循环
为了正确执行循环,我们需要对贝塞尔样条曲线做些改变。首先,SetControlPointMode需要确保第一和最后一个模式在循环中保持相同。
[C#] 纯文本查看 复制代码
public void SetControlPointMode (int index, BezierControlPointMode mode) {
                int modeIndex = (index + 1) / 3;
                modes[modeIndex] = mode;
                if (loop) {
                        if (modeIndex == 0) {
                                modes[modes.Length - 1] = mode;
                        }
                        else if (modeIndex == modes.Length - 1) {
                                modes[0] = mode;
                        }
                }
                EnforceMode(index);
        }

接着,EnforceMode在末点保持当不循环的时候。不得不检测固定或执行的点是否绕着数组。
[C#] 纯文本查看 复制代码
public void SetControlPoint (int index, Vector3 point) {
                if (index % 3 == 0) {
                        Vector3 delta = point - points[index];
                        if (loop) {
                                if (index == 0) {
                                        points[1] += delta;
                                        points[points.Length - 2] += delta;
                                        points[points.Length - 1] = point;
                                }
                                else if (index == points.Length - 1) {
                                        points[0] = point;
                                        points[1] += delta;
                                        points[index - 1] += delta;
                                }
                                else {
                                        points[index - 1] += delta;
                                        points[index + 1] += delta;
                                }
                        }
                        else {
                                if (index > 0) {
                                        points[index - 1] += delta;
                                }
                                if (index + 1 < points.Length) {
                                        points[index + 1] += delta;
                                }
                        }
                }
                points[index] = point;
                EnforceMode(index);
        }

最后,我们也不得不考虑循环当添加一条曲线到样条。结果可能是缠绕的,但仍然保持正确的圈形。
[C#] 纯文本查看 复制代码
public void AddCurve () {
                Vector3 point = points[points.Length - 1];
                Array.Resize(ref points, points.Length + 3);
                point.x += 1f;
                points[points.Length - 3] = point;
                point.x += 1f;
                points[points.Length - 2] = point;
                point.x += 1f;
                points[points.Length - 1] = point;

                Array.Resize(ref modes, modes.Length + 1);
                modes[modes.Length - 1] = modes[modes.Length - 2];
                EnforceMode(points.Length - 4);

                if (loop) {
                        points[points.Length - 1] = points[0];
                        modes[modes.Length - 1] = modes[0];
                        EnforceMode(0);
                }
        }

样条曲线圈
我们有循环很好,但不方便。我们不能再看到样条从哪里开始的。让BezierSplineInspector总是双倍第一个点的大小会使这个结果更明显。

注意圈圈的最后一个点会画在它上面,因此如果你点击你选择的最后一个点的中间,如果你点击的远离你获得第一个点的中心。
[C#] 纯文本查看 复制代码
private Vector3 ShowPoint (int index) {
                Vector3 point = handleTransform.TransformPoint(spline.GetControlPoint(index));
                float size = HandleUtility.GetHandleSize(point);
                if (index == 0) {
                        size *= 2f;
                }
                Handles.color = modeColors[(int)spline.GetControlPointMode(index)];
                if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
                        selectedIndex = index;
                        Repaint();
                }
                if (selectedIndex == index) {
                        EditorGUI.BeginChangeCheck();
                        point = Handles.DoPositionHandle(point, handleRotation);
                        if (EditorGUI.EndChangeCheck()) {
                                Undo.RecordObject(spline, "Move Point");
                                EditorUtility.SetDirty(spline);
                                spline.SetControlPoint(index, handleTransform.InverseTransformPoint(point));
                        }
                }
                return point;
        }

我们开始画大的
使用样条

我们一直在做样条,但我们没有用它来做任何事。可以用样条做数不清的事,比如沿着路径移动一个物体。我们创建一个SplineWalker部分。
[C#] 纯文本查看 复制代码
using UnityEngine;

public class SplineWalker : MonoBehaviour {

        public BezierSpline spline;

        public float duration;

        private float progress;

        private void Update () {
                progress += Time.deltaTime / duration;
                if (progress > 1f) {
                        progress = 1f;
                }
                transform.localPosition = spline.GetPoint(progress);
        }
}

现在我们创建一个walker对象,调整样条,设置区间,我们进入游戏模式后,观察它移动。我简单地使用立方体并用小的立方体来模拟眼睛,因此你可以看到它在看的方向。
沿着样条曲线
walker现在在移动,但没有沿着要去的方向移动。我们可以添加操作。
[C#] 纯文本查看 复制代码
public bool lookForward;

        private void Update () {
                progress += Time.deltaTime / duration;
                if (progress > 1f) {
                        progress = 1f;
                }
                Vector3 position = spline.GetPoint(progress);
                transform.localPosition = position;
                if (lookForward) {
                        transform.LookAt(position + spline.GetDirection(progress));
                }
        }

看看你要到哪里
另一个操作在环绕样条,而不是刚刚的移动。当我们盯着它的时候,我们可以使得walker返回去,通过样条。我们创建枚举来在这些模式中进行选择。
[C#] 纯文本查看 复制代码
public enum SplineWalkerMode {
        Once,
        Loop,
        PingPong
}

现在SplineWalker不得不记住它是否前进或后退。也需要调整进程当通过样条终点依据与它的模式。
[C#] 纯文本查看 复制代码
public SplineWalkerMode mode;

        private bool goingForward = true;

        private void Update () {
                if (goingForward) {
                        progress += Time.deltaTime / duration;
                        if (progress > 1f) {
                                if (mode == SplineWalkerMode.Once) {
                                        progress = 1f;
                                }
                                else if (mode == SplineWalkerMode.Loop) {
                                        progress -= 1f;
                                }
                                else {
                                        progress = 2f - progress;
                                        goingForward = false;
                                }
                        }
                }
                else {
                        progress -= Time.deltaTime / duration;
                        if (progress < 0f) {
                                progress = -progress;
                                goingForward = true;
                        }
                }

                Vector3 position = spline.GetPoint(progress);
                transform.localPosition = position;
                if (lookForward) {
                        transform.LookAt(position + spline.GetDirection(progress));
                }
        }
不同方向的步行
另一件我们可以做的事是去创建一个装饰器。当装饰器唤醒的时候能够实例化沿着样条曲线的一系列点。我们也可以给装饰器一个向前看的操作,能够用于点复制。一定频率的点的序列,允许复制。当然,如果序列是零活没有点,我们什么都没做。
我们需要一些点,因此为了达到这个目的创建一些预设。
[C#] 纯文本查看 复制代码
using UnityEngine;

public class SplineDecorator : MonoBehaviour {

        public BezierSpline spline;

        public int frequency;

        public bool lookForward;

        public Transform[] items;

        private void Awake () {
                if (frequency <= 0 || items == null || items.Length == 0) {
                        return;
                }
                float stepSize = 1f / (frequency * items.Length);
                for (int p = 0, f = 0; f < frequency; f++) {
                        for (int i = 0; i < items.Length; i++, p++) {
                                Transform item = Instantiate(items[i]) as Transform;
                                Vector3 position = spline.GetPoint(p * stepSize);
                                item.transform.localPosition = position;
                                if (lookForward) {
                                        item.transform.LookAt(position + spline.GetDirection(p * stepSize));
                                }
                                item.transform.parent = transform;
                        }
                }
        }
}

装饰样条曲线
圆圈做得不错,但不能一直到样条的末端,因为不是圆圈。我们通过提高我们的区间大小来遮盖样条的整个长度来修复这个问题,只要不是圈圈我们有很多地方可以放。
[C#] 纯文本查看 复制代码
if (frequency <= 0 || items == null || items.Length == 0) {
                        return;
                }
                float stepSize = frequency * items.Length;
                if (spline.Loop || stepSize == 1) {
                        stepSize = 1f / stepSize;
                }
                else {
                        stepSize = 1f / (stepSize - 1);

一直沿着走
有多种方法可以用样条,有许多特性可以添加到样条中。比如移除曲线或者把曲线分成两个小的,或者把两条曲线融合在一起。也有其他的样条类型可以探索,比如Centripetal Catmull-rom或者NURB.如果你对贝塞尔曲线很舒服,你也可以处理这些。所以教程结束了,尝试走走自己的路径。


喜欢这个教程?帮我成为资助者!
原文作者:Unity  翻译:Brittany


回复

使用道具 举报

5熟悉之中
734/1000
排名
3693
昨日变化
4

0

主题

181

帖子

734

积分

Rank: 5Rank: 5

UID
184269
好友
0
蛮牛币
864
威望
0
注册时间
2016-11-16
在线时间
237 小时
最后登录
2017-12-4
发表于 2016-11-21 18:38:32 | 显示全部楼层
太棒了,这个真的是为人民服务,谢谢。

回复 支持 反对

使用道具 举报

排名
21390
昨日变化
19

0

主题

4

帖子

35

积分

Rank: 1

UID
179037
好友
0
蛮牛币
48
威望
0
注册时间
2016-10-28
在线时间
15 小时
最后登录
2016-12-2
发表于 2016-11-22 08:43:50 | 显示全部楼层
非常感谢楼主翻译并引进重要的编辑器的应用案例,太有启发了,谢谢!!!!!!

回复 支持 反对

使用道具 举报

3偶尔光临
263/300
排名
6984
昨日变化
3

1

主题

46

帖子

263

积分

Rank: 3Rank: 3Rank: 3

UID
178701
好友
1
蛮牛币
415
威望
0
注册时间
2016-10-27
在线时间
72 小时
最后登录
2017-6-8
发表于 2016-11-22 09:21:39 | 显示全部楼层
太厉害了我的楼主
[发帖际遇]: 1823229308 发帖时在路边捡到 1 蛮牛币,偷偷放进了口袋. 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

7日久生情
1796/5000
排名
1663
昨日变化
10

9

主题

293

帖子

1796

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
159103
好友
6
蛮牛币
4960
威望
0
注册时间
2016-8-29
在线时间
796 小时
最后登录
2017-12-13

活力之星

发表于 2016-11-22 16:35:27 | 显示全部楼层
厉害了我的哥   你这个真是无敌啊    太强大了   学习到了   谢谢楼主分享

回复 支持 反对

使用道具 举报

5熟悉之中
849/1000
排名
2484
昨日变化
4

1

主题

47

帖子

849

积分

Rank: 5Rank: 5

UID
9175
好友
1
蛮牛币
1341
威望
0
注册时间
2013-11-28
在线时间
331 小时
最后登录
2017-12-8
发表于 2016-11-23 11:59:10 | 显示全部楼层
怒赞,学了好多新知识,感谢

回复 支持 反对

使用道具 举报

3偶尔光临
192/300
排名
21390
昨日变化
19

17

主题

24

帖子

192

积分

Rank: 3Rank: 3Rank: 3

UID
129264
好友
3
蛮牛币
324
威望
0
注册时间
2015-11-18
在线时间
75 小时
最后登录
2017-11-21

蛮牛译员

 楼主| 发表于 2016-11-23 21:28:11 | 显示全部楼层
yaozhl 发表于 2016-11-21 18:38
太棒了,这个真的是为人民服务,谢谢。

不谢,互相学习

回复 支持 反对

使用道具 举报

排名
2607
昨日变化

16

主题

172

帖子

1573

积分

Rank: 9Rank: 9Rank: 9

UID
22288
好友
9
蛮牛币
2487
威望
0
注册时间
2014-4-21
在线时间
329 小时
最后登录
2017-9-7

专栏作家

QQ
发表于 2016-12-3 23:09:37 | 显示全部楼层
楼主头像挺萌的 我就来点个赞

回复 支持 反对

使用道具 举报

5熟悉之中
751/1000
排名
4641
昨日变化
36

3

主题

105

帖子

751

积分

Rank: 5Rank: 5

UID
138025
好友
1
蛮牛币
462
威望
0
注册时间
2016-3-1
在线时间
395 小时
最后登录
2017-12-12
发表于 2017-1-19 16:11:06 | 显示全部楼层
这里有一个错误
http://www.manew.com/forum.php?mod=attachment&aid=MTI2MDk4fDIxZjNmZWI5ZTMzODgxNzAzZTViZTI2Njk4MGNjYmI3fDE1MTMxNDk2MjU%3D&request=yes&_f=.png
应该是points[0]的,下面的那个函数也是,然后我又去看了原作者的博客,发现也是有这个错误,最后就直接下载了
作者的demo来看看,学习学习一下,发现最后完成的效果:
http://www.manew.com/forum.php?mod=attachment&aid=MTI2MDk5fGViY2YyYmViNjUyODY0ZjJjOWZjOTZhMDAzZWM3NzQxfDE1MTMxNDk2MjU%3D&request=yes&_f=.png
1.png
2.png

回复 支持 反对

使用道具 举报

排名
40323
昨日变化
31

0

主题

47

帖子

51

积分

Rank: 2Rank: 2

UID
218693
好友
0
蛮牛币
65
威望
0
注册时间
2017-4-20
在线时间
3 小时
最后登录
2017-4-22
发表于 2017-4-21 15:20:23 | 显示全部楼层
添加GetPoint方法到贝塞尔曲线 可以可以

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表