[Unity] Dreamteck Splines实现沿路径移动功能

Dreamteck Splines实现沿路径移动功能

最近有一个"让物体沿固定路径移动"的需求,因此接触到了Dreamteck Splines插件。

Dreamteck Splines可以很方便地绘制各种插值曲线,但在实现物体移动的时候却遇到了很多坑,因此在这里记录一下。

1. 绘制路径线

首先,让我们在场景上创建一个空物体,并添加SplineComputer组件。

由于我这是个2D项目,所以选择在Z平面上绘制。

之后编辑器中就会显示出跟随鼠标的网格线,点击左键就可以逐点绘制Spline了。

在右侧选项中可以修改Spline的类型。默认类型是Catmull Rom,我们可以把它改成直线Linear

2. 获取坐标

SplineComputer类有两类获取坐标点的方法:

  • GetPoint(int)
  • GetPoints()

这两个方法用于获取我们手动添加的坐标点,也就是我们上图中的那三个点。这明显不符合我们"沿路径移动"的需求。

而下面这三个方法才是返回Spline上的所有坐标点

  • Evaluate(double)
  • EvaluatePosition(double)
  • EvaluatePositions()

其中,EvaluateEvaluatePosition的区别在于,Evaluate返回的是SplineSample对象,包括了坐标、朝向、颜色、百分比等信息,而EvaluatePosition则是简单地返回一个Vector3的坐标。在只需要坐标的情况下,推荐使用更加轻量化的EvaluatePosition

3. 移动示例(踩坑)

EvaluatePosition(double)方法传入一个0~1的值,就会返回Spline上对应的坐标,因此我们可以用这个脚本手动模拟物体的移动过程。

C# 复制代码
using Dreamteck.Splines;
using UnityEngine;

public class Move : MonoBehaviour
{
    [Range(0, 1)]
    public float Percent;
    public Transform Target;
    private SplineComputer spline;


    void Start()
    {
        spline = GetComponent<SplineComputer>();
    }

    void Update()
    {
        Target.position = spline.EvaluatePosition(Percent);
    }
}

将脚本挂载到SplineComputer所在的物体上,拖动右侧的滑动条即可移动目标物体。

一切看似十分正常,直到我们又添加了一条长度不同的线段。

可以明显地看出,在后面这个较短的路径中,物体的移动速度明显变慢了。

当我们直接将进度设为0.5后,便能发现问题所在。

目标物体移动并没有移动到Spline的终点,而是移动到了我们设置的第二个控制点上。

这个问题在官方文档3.3. Sample Mode中有对应的解答:

默认情况下,样条曲线(Spline)在 [0-1] 的百分比范围内进行计算(evaluated),涵盖了所有坐标点。

例如,一条由 3 个点组成的样条曲线,计算百分比为 0.5 的坐标点,将始终返回第二个点的位置,因为它位于中间。

然而,如果第一个点和第二个点非常接近,而第三个点距离它们很远,计算百分比为 0.5 的坐标点不会返回样条曲线的中间位置 ,它仍将返回第二个点。因为某些区域的采样点比其他区域更密集

为了说明这一点,以下是显示了采样点密度的样条曲线:每条垂直线表示一个采样点(spline sample)。在这种情况下,点 1 和点 2 之间有 10 个采样点,但点 2 和点 3 之间也只有 10 个采样点。

从中我们可以看出,问题的根源在于,Evaluate参数中的percent并不是指Spline长度的百分比,而是表示Spline采样点的百分比。而采样点的不均匀分布,导致了采样点百分比和长度百分比不一致的情况。

5 方法一:修改采样模式(Sample Mode)

前面我们提到"采样点的不均匀分布,导致了采样点百分比和长度百分比不一致的情况"。

反过来说,我们只需要让采样点能够均匀分布,就可以解决这一问题。

SplineComputer提供了三种采样模式(Sample Mode):

  • Default(默认):两点间的采样点数量固定
  • Uniform(均匀):根据Spline长度,均匀分布采样点。但在Spline较长时会有更大的性能开销。
  • Optimized(优化):与默认模式相同,但会执行优化操作删除不必要的采样点

所以我们需要选择Uniform模式,以实现均匀分布采样点的需求。

请注意,在Default和Optimized模式下,当移动控制点时,Spline仅更新受该点影响的区域中的采样点。而在Uniform模式下,将重新计算整个Spline。在Optimized模式下,还提供了一个额外的滑块来控制优化的角度阈值。

这样一来就能正确地匀速移动目标了

但是!

除了性能开销外,这个方法还会带来一系列问题。

首先,它会导致线段脱离控制点:

其次,它还有个很致命的BUG

Uniform模式下,如果你用CalculateLength方法获取Spline的长度,那么初始状态下将会始终返回0。此时必须对他"进行一些操作",比如移动控制点,修改其他参数等,让他响应一次变化。之后CalculateLength才能正确返回数值。

6 方法二:使用Travel函数(推荐)

为了避免上述问题,我们可以使用Travel函数计算某个长度在Spline上对应的采样点百分比。

它的使用方法在官方文档20.4. Converting World Units to Spline Percentages中有所提及。

假如我们要获取Spline中心点的坐标,只需要传入Spline长度的一半,也就是spline.CalculateLength() / 2,然后Travel函数就会返回对应的percent。这时再调用EvaluatePosition(percent)即可得到中心点的坐标。其他位置的坐标也是同理,我们只需要给出对应的长度即可获取坐标。

这样一来,我们就可以方便地实现沿路径匀速移动的功能了。

C# 复制代码
using Dreamteck.Splines;
using UnityEngine;

public class Move : MonoBehaviour
{
    [Range(0, 1)]
    public double Percent;
    public float Speed;
    public Transform Target;
    private float distance;
    private SplineComputer spline;


    void Start()
    {
        spline = GetComponent<SplineComputer>();
        distance = 0;
    }

    void Update()
    {
        distance += Speed * Time.deltaTime;
        // 有需要的话可以用这个限制上限
        // distance = Math.Min(distance, spline.CalculateLength()); 
        Percent = spline.Travel(0, distance);
        Target.position = spline.EvaluatePosition(Percent);
        
        if (Percent == 1)
        {
            // do something
            Debug.Log("Done");
        }
    }
}

效果如下

参考资料

Dreamteck Splines -- User Manual


本文发布于2024年7月14日

最后编辑于2024年7月14日

相关推荐
与火星的孩子对话2 小时前
Unity3D开发AI桌面精灵/宠物系列 【三】 语音识别 ASR 技术、语音转文本多平台 - 支持科大讯飞、百度等 C# 开发
人工智能·unity·c#·游戏引擎·语音识别·宠物
向宇it2 小时前
【零基础入门unity游戏开发——2D篇】2D 游戏场景地形编辑器——TileMap的使用介绍
开发语言·游戏·unity·c#·编辑器·游戏引擎
牙膏上的小苏打233319 小时前
Unity Surround开关后导致获取主显示器分辨率错误
unity·主屏幕
Unity大海21 小时前
诠视科技Unity SDK开发环境配置、项目设置、apk打包。
科技·unity·游戏引擎
浅陌sss1 天前
Unity中 粒子系统使用整理(一)
unity·游戏引擎
维度攻城狮1 天前
实现在Unity3D中仿真汽车,而且还能使用ros2控制
python·unity·docker·汽车·ros2·rviz2
为你写首诗ge1 天前
【Unity网络编程知识】FTP学习
网络·unity
神码编程1 天前
【Unity】 HTFramework框架(六十四)SaveDataRuntime运行时保存组件参数、预制体
unity·编辑器·游戏引擎
菲fay2 天前
Unity 单例模式写法
unity·单例模式
火一线2 天前
【Framework-Client系列】UIGenerate介绍
游戏·unity