Unity中如何判定用户绘制线条与目标线条的相似度?
嘿,我来帮你搞定这个线条相似度判断的问题!你已经有了基础的绘制逻辑,现在只需要加上特征标准化和相似度计算的逻辑,就能区分出像绿色那样的正确线条和红色那样的错误线条了。
下面是一步步的具体实现方案,直接可以结合你的现有代码来用:
核心思路
要判断两条线条相似,不能直接对比每个点的位置(用户画的点数量、速度都不一样),得先把两条线的位置、大小、采样密度标准化,再用合适的算法计算相似度,最后和阈值对比来判定是否正确。
第一步:准备目标线条数据
首先你需要把目标线条(蓝色那条)的点数据存下来,方便后续对比。在你的DrawLine类里加几个变量:
public float similarityThreshold = 0.1f; // 可调整的判定阈值,越小要求越严格 public LineRenderer targetLineRenderer; // 在Inspector里拖入目标线条的LineRenderer组件 private List<Vector2> normalizedTargetPoints; // 标准化后的目标线条点
然后在Start方法里初始化目标线条的标准化数据:
void Start() { if (targetLineRenderer != null) { // 从目标LineRenderer中提取点,并做标准化 normalizedTargetPoints = NormalizeLine(GetLinePointsFromRenderer(targetLineRenderer)); } } // 从LineRenderer中提取点的工具方法 private List<Vector2> GetLinePointsFromRenderer(LineRenderer lr) { List<Vector2> points = new List<Vector2>(); for (int i = 0; i < lr.positionCount; i++) { // 注意:要和你绘制时的坐标空间一致,这里用世界坐标转屏幕坐标,和你的绘制逻辑匹配 points.Add(Camera.main.WorldToScreenPoint(lr.GetPosition(i))); } return points; }
第二步:标准化线条数据
这一步是关键,用来消除两条线的位置、大小、采样数量差异:
private List<Vector2> NormalizeLine(List<Vector2> points) { if (points.Count < 2) return new List<Vector2>(); // 1. 平移到原点:把线条的起点移到(0,0),消除位置差异 Vector2 offset = points[0]; List<Vector2> translatedPoints = points.Select(p => p - offset).ToList(); // 2. 缩放至单位长度:把线条总长度缩为1,消除大小差异 float totalLength = 0; for (int i = 0; i < translatedPoints.Count - 1; i++) { totalLength += Vector2.Distance(translatedPoints[i], translatedPoints[i+1]); } if (totalLength == 0) return translatedPoints; List<Vector2> scaledPoints = translatedPoints.Select(p => p / totalLength).ToList(); // 3. 重采样固定数量的点:把线条统一采样为20个点,方便后续对比 List<Vector2> resampledPoints = new List<Vector2>(); float step = totalLength / 19; // 20个点需要19步 float currentDistance = 0; resampledPoints.Add(scaledPoints[0]); for (int i = 1; i < scaledPoints.Count; i++) { float segmentLength = Vector2.Distance(scaledPoints[i-1], scaledPoints[i]); while (currentDistance + segmentLength >= step) { float ratio = (step - currentDistance) / segmentLength; Vector2 interpolatedPoint = Vector2.Lerp(scaledPoints[i-1], scaledPoints[i], ratio); resampledPoints.Add(interpolatedPoint); currentDistance += segmentLength; step += totalLength / 19; } currentDistance += segmentLength; } // 确保最后一个点被加入 if (resampledPoints.Count < 20) { resampledPoints.Add(scaledPoints.Last()); } return resampledPoints; }
第三步:相似度计算(推荐用DTW算法)
这里推荐用动态时间规整(DTW),它能很好地应对用户绘制速度不同导致的点数量差异,比简单的逐点对比更准确:
private float CalculateDTWSimilarity(List<Vector2> lineA, List<Vector2> lineB) { int n = lineA.Count; int m = lineB.Count; float[,] costMatrix = new float[n, m]; // 初始化矩阵的第一行和第一列 costMatrix[0, 0] = Vector2.Distance(lineA[0], lineB[0]); for (int i = 1; i < n; i++) { costMatrix[i, 0] = costMatrix[i-1, 0] + Vector2.Distance(lineA[i], lineB[0]); } for (int j = 1; j < m; j++) { costMatrix[0, j] = costMatrix[0, j-1] + Vector2.Distance(lineA[0], lineB[j]); } // 填充成本矩阵 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { float pointDistance = Vector2.Distance(lineA[i], lineB[j]); costMatrix[i, j] = pointDistance + Mathf.Min( Mathf.Min(costMatrix[i-1, j], costMatrix[i, j-1]), costMatrix[i-1, j-1] ); } } // 返回归一化后的相似度分数(值越小越相似) return costMatrix[n-1, m-1] / (n + m); }
如果你想要更简单的方案,也可以用逐点平均距离,但精度会差一些:
private float CalculateAverageDistance(List<Vector2> lineA, List<Vector2> lineB) { if (lineA.Count != lineB.Count) return float.MaxValue; float totalDistance = 0; for (int i = 0; i < lineA.Count; i++) { totalDistance += Vector2.Distance(lineA[i], lineB[i]); } return totalDistance / lineA.Count; }
第四步:集成到你的绘制逻辑中
修改Update方法里的鼠标抬起事件,当用户完成绘制时触发相似度判断:
if (Input.GetMouseButtonUp(0)) { mouseButtonState++; // 当用户完成一次绘制后(根据你的mouseButtonState逻辑,这里假设状态为2时完成) if (mouseButtonState == 2) { List<Vector2> normalizedUserPoints = NormalizeLine(fingerPositions); if (normalizedTargetPoints.Count > 0 && normalizedUserPoints.Count > 0) { float similarityScore = CalculateDTWSimilarity(normalizedTargetPoints, normalizedUserPoints); // 或者用CalculateAverageDistance(normalizedTargetPoints, normalizedUserPoints) if (similarityScore < similarityThreshold) { Debug.Log("绘制正确!(像绿色线条那样)"); // 这里添加正确后的逻辑:比如加分、显示成功提示、保留线条 } else { Debug.Log("绘制不正确!(像红色线条那样)"); // 这里添加错误后的逻辑:比如销毁当前线条、允许重新绘制 Destroy(currentLine); mouseButtonState = 0; // 重置状态,让用户可以重新画 } } } }
一些优化建议
- 过滤抖动噪声:用户绘制时可能有手抖,你可以在
UpdateLine方法里加入平滑逻辑,比如只当新点和上一个点的距离超过0.2f才添加,或者用移动平均处理点数据。 - 方向无关判定:如果不需要考虑绘制方向(比如从左到右和从右到左算一样),可以把用户的线条反转后再计算一次相似度,取最小的分数。
- 可视化反馈:判定后给用户直观反馈,比如把正确的线条改成绿色,错误的改成红色,或者弹出提示文本。
- 阈值调试:
similarityThreshold需要根据你的实际场景调整,多测试几次找到最合适的数值。
内容的提问来源于stack exchange,提问作者liel7200
相关产品推荐
相关产品推荐

