<think>我们注意到用户反馈“debug没有出现”,这里的问题可能在于调试可视化部分。在Unity中,`OnDrawGizmos`方法默认只在Scene视图显示,并且需要确保:
1. 脚本所在物体在Scene视图中被选中(或者Gizmos全局开启)
2. 脚本中的`debugVisualization`被设置为true(在Inspector中勾选)
3. 关键点坐标在合理范围内(否则Gizmos可能绘制在不可见位置)
我们将进行以下调整:
- 添加一个公共变量来控制是否在Game视图也绘制(通过UI)
- 在`OnDrawGizmos`中确保坐标转换到世界坐标(如果当前坐标是归一化坐标,需要转换)
- 添加一个简单的UI文本用于在Game视图显示角度和速度
但是,用户只提到debug没有出现,我们首先确保Gizmos绘制正确。由于关键点是归一化坐标(0-1),而Unity的Scene视图通常使用世界坐标,因此我们需要将归一化坐标转换为屏幕坐标或世界坐标。
然而,在HolisticTrackingSolution中,通常已经提供了将归一化坐标转换为屏幕坐标或世界坐标的方法。我们假设用户希望在世界坐标中绘制,但归一化坐标需要转换。
由于我们没有直接可用的转换,我们可以考虑使用一个参考物体(如摄像机)来转换坐标。但为了简化,我们假设用户希望直接在屏幕空间绘制(使用GUI)。
我们将添加两种调试方式:
1. Gizmos在Scene视图绘制(使用世界坐标,需要转换归一化坐标到世界坐标,这里我们假设世界坐标是平面,深度为0)
2. GUI在Game视图绘制文本
由于用户的问题描述不详细,我们将提供两种方案。
但是,用户只要求解决“debug没有出现”,我们首先确保Gizmos在Scene视图可见。
修改后的代码:
我们添加一个公共变量来设置绘制平面(假设在z=0的平面上绘制),并调整坐标转换。同时,我们添加一个在Game视图显示关键数据的UI。
但是,由于用户没有提供具体的场景设置,我们将采用一个保守的方法:在摄像机前创建一个虚拟平面来映射关键点。
然而,为了快速解决问题,我们也可以直接在屏幕空间绘制(使用GUI)。
我们选择添加一个简单的GUI文本显示角度和速度,同时保留Gizmos绘制(但将归一化坐标转换为屏幕坐标)。
由于时间关系,我们提供以下修改:
1. 修改Gizmos绘制为屏幕坐标(需要将归一化坐标乘以屏幕尺寸),但Gizmos默认在世界坐标绘制,所以我们需要将屏幕坐标转换为世界坐标(需要摄像机参考)。
2. 或者,我们使用Debug.DrawLine,它可以在世界坐标中绘制,但同样需要坐标转换。
这里,我们假设用户希望将关键点绘制在距离摄像机固定距离的平面上。
我们将添加一个摄像机参考和绘制距离:
```csharp
public Camera targetCamera; // 用于坐标转换的摄像机
public float drawDistance = 10f; // 绘制平面距离摄像机的距离
```
然后,在ProcessPoseLandmarks中,我们将归一化坐标转换为世界坐标:
```csharp
// 将归一化坐标转换为屏幕坐标(0-1 -> 0到屏幕宽高)
Vector3 hipScreen = new Vector3(hip.X * Screen.width, (1-hip.Y) * Screen.height, drawDistance);
Vector3 kneeScreen = new Vector3(knee.X * Screen.width, (1-knee.Y) * Screen.height, drawDistance);
Vector3 ankleScreen = new Vector3(ankle.X * Screen.width, (1-ankle.Y) * Screen.height, drawDistance);
// 转换为世界坐标
debugHipPos = targetCamera.ScreenToWorldPoint(hipScreen);
debugKneePos = targetCamera.ScreenToWorldPoint(kneeScreen);
debugAnklePos = targetCamera.ScreenToWorldPoint(ankleScreen);
```
但是,这需要用户指定targetCamera,并且Screen.width/height在非运行时可能为0。
因此,我们修改为在OnDrawGizmos中实时转换(如果targetCamera不为空)。
然而,为了简化,我们暂时不修改坐标转换,而是假设用户的关键点坐标已经是世界坐标(实际上不是,归一化坐标需要转换)。
由于用户之前代码没有转换,所以Gizmos绘制的位置可能是(0,0,0)附近(归一化坐标值在0-1,所以绘制的球体非常小,且靠近原点)。
解决方案:将归一化坐标乘以一个缩放因子(例如10)以便在Scene视图中看到。
我们暂时这样做:
```csharp
// 存储用于调试可视化(放大坐标)
float scale = 10f;
debugHipPos = new Vector3(hip.X, 1 - hip.Y, 0) * scale;
debugKneePos = new Vector3(knee.X, 1 - knee.Y, 0) * scale;
debugAnklePos = new Vector3(ankle.X, 1 - ankle.Y, 0) * scale;
```
同时,我们添加一个GUI显示:
```csharp
private string debugText = "";
void OnGUI()
{
if (debugVisualization)
{
GUI.Label(new Rect(10, 10, 300, 20), debugText);
}
}
// 在ProcessPoseLandmarks中:
debugText = $"角度: {angle:F1}°, 速度: {ankleSpeed:F1}单位/秒";
```
综合以上,修改后的完整代码:
注意:我们添加了targetCamera和scale因子,但为了简单,我们先用scale因子放大坐标以便在Scene视图看到。同时添加了GUI显示。
但是,用户可能没有设置targetCamera,所以我们先不使用摄像机转换,而是直接放大坐标。
修改后的代码部分:
```csharp
// 在类中添加
public float debugScale = 10f; // 用于可视化的缩放因子
private string debugText = "";
// 在ProcessPoseLandmarks中:
// 存储用于调试可视化(放大坐标)
debugHipPos = new Vector3(hip.X, 1 - hip.Y, 0) * debugScale;
debugKneePos = new Vector3(knee.X, 1 - knee.Y, 0) * debugScale;
debugAnklePos = new Vector3(ankle.X, 1 - ankle.Y, 0) * debugScale;
// 更新GUI文本
debugText = $"角度: {angle:F1}°, 速度: {ankleSpeed:F1}单位/秒";
// 添加OnGUI方法
void OnGUI()
{
if (debugVisualization)
{
GUI.color = Color.red;
GUI.skin.label.fontSize = 20;
GUI.Label(new Rect(10, 10, 500, 30), debugText);
}
}
```
完整修改后的代码:
由于代码较长,这里只提供修改后的部分,但用户需要替换整个脚本。
但是,我们重新整理完整代码:
```csharp
using UnityEngine;
using System.Collections.Generic;
using Mediapipe.Unity.Sample.Holistic;
using System;
using Mediapipe;
using Mediapipe.Unity;
using Google.Protobuf.Collections;
using System.Reflection;
using System.Linq;
using Color = UnityEngine.Color;
public class KickDetector : MonoBehaviour
{
// 关键点索引
const int LEFT_HIP = 23;
const int LEFT_KNEE = 25;
const int LEFT_ANKLE = 27;
const int RIGHT_HIP = 24;
const int RIGHT_KNEE = 26;
const int RIGHT_ANKLE = 28;
// 动作参数
public float kickAngleThreshold = 120f;
public float kickSpeedThreshold = 5f;
private Vector3 lastAnklePos;
private float lastEventTime;
private long lastTimestamp;
// 调试可视化
public bool debugVisualization = true;
public float debugScale = 10f; // 可视化缩放因子
private Vector3 debugHipPos, debugKneePos, debugAnklePos;
private string debugText = "";
void Start()
{
var solution = GetComponent<HolisticTrackingSolution>();
if (solution != null)
{
SubscribeToEvent(solution);
}
else
{
Debug.LogError("HolisticTrackingSolution component not found!");
}
lastEventTime = Time.time;
}
private void SubscribeToEvent(HolisticTrackingSolution solution)
{
var eventInfo = solution.GetType().GetEvent("OnPoseLandmarksOutput");
if (eventInfo != null)
{
var handler = Delegate.CreateDelegate(
eventInfo.EventHandlerType,
this,
nameof(HandlePoseLandmarksOutput)
);
eventInfo.AddEventHandler(solution, handler);
Debug.Log("成功订阅姿态关键点事件");
}
else
{
Debug.LogError("OnPoseLandmarksOutput事件未找到!");
}
}
void HandlePoseLandmarksOutput(object stream, object eventArgs)
{
try
{
var argsType = eventArgs.GetType();
var packetProperty = argsType.GetProperty("packet");
if (packetProperty == null)
{
Debug.LogWarning("事件参数中未找到packet属性");
return;
}
var packet = packetProperty.GetValue(eventArgs) as Packet<NormalizedLandmarkList>;
if (packet == null)
{
Debug.LogWarning("无法获取NormalizedLandmarkList数据包");
return;
}
long currentTimestamp = packet.TimestampMicroseconds();
var landmarkList = packet.Get(NormalizedLandmarkList.Parser);
if (landmarkList == null)
{
Debug.LogWarning("关键点列表为空");
return;
}
var landmarks = landmarkList.Landmark;
Debug.Log($"收到姿态数据: {landmarks.Count}个关键点");
ProcessPoseLandmarks(landmarks, currentTimestamp);
}
catch (Exception ex)
{
Debug.LogError($"处理姿态关键点时出错: {ex.Message}");
}
}
void ProcessPoseLandmarks(RepeatedField<NormalizedLandmark> landmarks, long timestamp)
{
if (landmarks == null || landmarks.Count < 29)
{
Debug.LogWarning($"关键点不足: {landmarks?.Count ?? 0}/29");
return;
}
// 获取右腿关键点
var hip = landmarks[RIGHT_HIP];
var knee = landmarks[RIGHT_KNEE];
var ankle = landmarks[RIGHT_ANKLE];
// 检查关键点置信度
if (hip.Visibility < 0.1f || knee.Visibility < 0.1f || ankle.Visibility < 0.1f)
{
Debug.Log($"关键点置信度过低: 髋({hip.Visibility:F2}) 膝({knee.Visibility:F2}) 踝({ankle.Visibility:F2})");
return;
}
// 转换为Unity坐标(反转Y轴)
Vector3 hipPos = new Vector3(hip.X, 1 - hip.Y, hip.Z);
Vector3 kneePos = new Vector3(knee.X, 1 - knee.Y, knee.Z);
Vector3 anklePos = new Vector3(ankle.X, 1 - ankle.Y, ankle.Z);
// 存储用于调试可视化(放大坐标)
debugHipPos = hipPos * debugScale;
debugKneePos = kneePos * debugScale;
debugAnklePos = anklePos * debugScale;
// 计算膝盖弯曲角度
Vector3 thighVector = hipPos - kneePos;
Vector3 calfVector = anklePos - kneePos;
float angle = Vector3.Angle(thighVector, calfVector);
// 计算脚踝移动速度
float ankleSpeed = 0;
if (lastTimestamp > 0 && timestamp > lastTimestamp)
{
float deltaSeconds = (timestamp - lastTimestamp) / 1000000f;
if (deltaSeconds > 0)
{
ankleSpeed = Vector3.Distance(anklePos, lastAnklePos) / deltaSeconds;
}
}
// 更新状态
lastAnklePos = anklePos;
lastTimestamp = timestamp;
Debug.Log($"角度: {angle:F1}°, 速度: {ankleSpeed:F1}单位/秒, 冷却: {Time.time - lastEventTime:F1}秒");
debugText = $"角度: {angle:F1}°, 速度: {ankleSpeed:F1}单位/秒";
// 踢球动作判断
if (angle > kickAngleThreshold &&
ankleSpeed > kickSpeedThreshold &&
Time.time - lastEventTime > 1f)
{
Debug.Log($"✅ 踢球动作检测! 角度:{angle:F1}° 速度:{ankleSpeed:F1}单位/秒");
lastEventTime = Time.time;
// 触发游戏逻辑...
}
}
// 调试可视化
void OnDrawGizmos()
{
if (!debugVisualization) return;
// 绘制腿部线条
Gizmos.color = Color.red;
Gizmos.DrawLine(debugHipPos, debugKneePos);
Gizmos.DrawLine(debugKneePos, debugAnklePos);
// 标记关键点
Gizmos.color = Color.green;
Gizmos.DrawSphere(debugHipPos, 0.1f * debugScale); // 球体大小也缩放
Gizmos.DrawSphere(debugKneePos, 0.1f * debugScale);
Gizmos.DrawSphere(debugAnklePos, 0.1f * debugScale);
}
void OnGUI()
{
if (debugVisualization)
{
GUI.color = Color.red;
GUI.skin.label.fontSize = 20;
GUI.Label(new Rect(10, 10, 500, 30), debugText);
}
}
void OnDestroy()
{
var solution = GetComponent<HolisticTrackingSolution>();
if (solution != null)
{
var eventInfo = solution.GetType().GetEvent("OnPoseLandmarksOutput");
if (eventInfo != null)
{
try
{
var handler = Delegate.CreateDelegate(
eventInfo.EventHandlerType,
this,
nameof(HandlePoseLandmarksOutput)
);
eventInfo.RemoveEventHandler(solution, handler);
}
catch (Exception ex)
{
Debug.LogWarning($"取消订阅时出错: {ex.Message}");
}
}
}
}
}
```
注意:我们通过`debugScale`(默认10)放大了Gizmos绘制的坐标,这样在Scene视图中就能看到。同时,我们在Game视图的左上角用GUI显示角度和速度。
用户需要根据实际情况调整`debugScale`的值(可以在Inspector中调整)。
这样,用户就可以在Scene视图中看到腿部关键点的连线,并在Game视图中看到实时数据。
如果还是看不到Gizmos,请确保:
- 在Scene视图上方点击Gizmos按钮(确保开启)
- 选中了挂载该脚本的物体
- debugVisualization在Inspector中被勾选
希望这样能解决debug没有出现的问题。
§§相关问题§§
1. 如何将MediaPipe的归一化坐标转换为Unity的世界坐标以匹配3D场景?
2. 在Unity中使用GUI显示实时数据有哪些替代方案(如UI Text)?
3. 当MediaPipe关键点数据不稳定时,有哪些滤波方法可以平滑关键点位置?
4. 如何扩展此脚本来检测其他动作(如挥拳、跳跃)?
5. 在多人姿态估计中,如何将关键点与不同玩家进行匹配?
</think>
您的调试可视化没有出现,这可能是由于坐标转换、Gizmos设置或Unity编辑器配置问题。以下是完整的修复方案:
### 调试可视化问题修复方案
```csharp
using UnityEngine;
using System.Collections.Generic;
using Mediapipe.Unity.Sample.Holistic;
using System;
using Mediapipe;
using Mediapipe.Unity;
using Google.Protobuf.Collections;
using System.Reflection;
using UnityEngine.UI; // 添加UI命名空间
public class KickDetector : MonoBehaviour
{
// 关键点索引
const int LEFT_HIP = 23;
const int LEFT_KNEE = 25;
const int LEFT_ANKLE = 27;
const int RIGHT_HIP = 24;
const int RIGHT_KNEE = 26;
const int RIGHT_ANKLE = 28;
// 动作参数
public float kickAngleThreshold = 120f;
public float kickSpeedThreshold = 5f;
private Vector3 lastAnklePos;
private float lastEventTime;
private long lastTimestamp;
// 调试可视化
public bool debugVisualization = true;
public bool showInGameDebug = true; // 新增游戏内调试显示
public GameObject debugCanvasPrefab; // 调试UI预制体
private GameObject debugCanvasInstance;
private Text debugText;
private Vector3 debugHipPos, debugKneePos, debugAnklePos;
void Start()
{
var solution = GetComponent<HolisticTrackingSolution>();
if (solution != null)
{
SubscribeToEvent(solution);
}
else
{
Debug.LogError("HolisticTrackingSolution component not found!");
}
lastEventTime = Time.time;
// 创建调试UI
if (showInGameDebug && debugCanvasPrefab != null)
{
debugCanvasInstance = Instantiate(debugCanvasPrefab);
debugText = debugCanvasInstance.GetComponentInChildren<Text>();
}
}
private void SubscribeToEvent(HolisticTrackingSolution solution)
{
var eventInfo = solution.GetType().GetEvent("OnPoseLandmarksOutput");
if (eventInfo != null)
{
var handler = Delegate.CreateDelegate(
eventInfo.EventHandlerType,
this,
nameof(HandlePoseLandmarksOutput)
);
eventInfo.AddEventHandler(solution, handler);
Debug.Log("成功订阅姿态关键点事件");
}
else
{
Debug.LogError("OnPoseLandmarksOutput事件未找到!");
}
}
void HandlePoseLandmarksOutput(object stream, object eventArgs)
{
try
{
var argsType = eventArgs.GetType();
var packetProperty = argsType.GetProperty("packet");
if (packetProperty == null)
{
Debug.LogWarning("事件参数中未找到packet属性");
return;
}
var packet = packetProperty.GetValue(eventArgs) as Packet<NormalizedLandmarkList>;
if (packet == null)
{
Debug.LogWarning("无法获取NormalizedLandmarkList数据包");
return;
}
long currentTimestamp = packet.TimestampMicroseconds();
var landmarkList = packet.Get(NormalizedLandmarkList.Parser);
if (landmarkList == null)
{
Debug.LogWarning("关键点列表为空");
return;
}
var landmarks = landmarkList.Landmark;
Debug.Log($"收到姿态数据: {landmarks.Count}个关键点");
ProcessPoseLandmarks(landmarks, currentTimestamp);
}
catch (Exception ex)
{
Debug.LogError($"处理姿态关键点时出错: {ex.Message}");
}
}
void ProcessPoseLandmarks(RepeatedField<NormalizedLandmark> landmarks, long timestamp)
{
if (landmarks == null || landmarks.Count < 29)
{
Debug.LogWarning($"关键点不足: {landmarks?.Count ?? 0}/29");
return;
}
// 获取右腿关键点
var hip = landmarks[RIGHT_HIP];
var knee = landmarks[RIGHT_KNEE];
var ankle = landmarks[RIGHT_ANKLE];
// 检查关键点置信度
if (hip.Visibility < 0.1f || knee.Visibility < 0.1f || ankle.Visibility < 0.1f)
{
Debug.Log($"关键点置信度过低: 髋({hip.Visibility:F2}) 膝({knee.Visibility:F2}) 踝({ankle.Visibility:F2})");
return;
}
// 转换为Unity坐标(反转Y轴)
// 注意:这里使用世界坐标而不是归一化坐标
Camera mainCamera = Camera.main;
if (mainCamera == null) return;
// 将归一化坐标转换为屏幕坐标
Vector3 hipScreen = new Vector3(hip.X * Screen.width, (1 - hip.Y) * Screen.height, 0);
Vector3 kneeScreen = new Vector3(knee.X * Screen.width, (1 - knee.Y) * Screen.height, 0);
Vector3 ankleScreen = new Vector3(ankle.X * Screen.width, (1 - ankle.Y) * Screen.height, 0);
// 转换为世界坐标(在摄像机前方10单位处)
debugHipPos = mainCamera.ScreenToWorldPoint(new Vector3(hipScreen.x, hipScreen.y, 10f));
debugKneePos = mainCamera.ScreenToWorldPoint(new Vector3(kneeScreen.x, kneeScreen.y, 10f));
debugAnklePos = mainCamera.ScreenToWorldPoint(new Vector3(ankleScreen.x, ankleScreen.y, 10f));
// 计算膝盖弯曲角度
Vector3 thighVector = debugHipPos - debugKneePos;
Vector3 calfVector = debugAnklePos - debugKneePos;
float angle = Vector3.Angle(thighVector, calfVector);
// 计算脚踝移动速度
float ankleSpeed = 0;
if (lastTimestamp > 0 && timestamp > lastTimestamp)
{
float deltaSeconds = (timestamp - lastTimestamp) / 1000000f;
if (deltaSeconds > 0)
{
ankleSpeed = Vector3.Distance(debugAnklePos, lastAnklePos) / deltaSeconds;
}
}
// 更新状态
lastAnklePos = debugAnklePos;
lastTimestamp = timestamp;
string debugInfo = $"角度: {angle:F1}°, 速度: {ankleSpeed:F1}单位/秒, 冷却: {Time.time - lastEventTime:F1}秒";
Debug.Log(debugInfo);
// 更新UI调试信息
if (debugText != null)
{
debugText.text = debugInfo;
}
// 踢球动作判断
if (angle > kickAngleThreshold &&
ankleSpeed > kickSpeedThreshold &&
Time.time - lastEventTime > 1f)
{
Debug.Log($"✅ 踢球动作检测! 角度:{angle:F1}° 速度:{ankleSpeed:F1}单位/秒");
lastEventTime = Time.time;
// 触发游戏逻辑...
}
}
// 调试可视化
void OnDrawGizmos()
{
if (!debugVisualization) return;
// 绘制腿部线条
Gizmos.color = Color.red;
Gizmos.DrawLine(debugHipPos, debugKneePos);
Gizmos.DrawLine(debugKneePos, debugAnklePos);
// 标记关键点
Gizmos.color = Color.green;
Gizmos.DrawSphere(debugHipPos, 0.1f);
Gizmos.DrawSphere(debugKneePos, 0.1f);
Gizmos.DrawSphere(debugAnklePos, 0.1f);
// 绘制标签
GUIStyle style = new GUIStyle();
style.normal.textColor = Color.white;
style.fontSize = 12;
UnityEditor.Handles.Label(debugHipPos, "髋关节", style);
UnityEditor.Handles.Label(debugKneePos, "膝盖", style);
UnityEditor.Handles.Label(debugAnklePos, "脚踝", style);
}
void OnDestroy()
{
var solution = GetComponent<HolisticTrackingSolution>();
if (solution != null)
{
var eventInfo = solution.GetType().GetEvent("OnPoseLandmarksOutput");
if (eventInfo != null)
{
try
{
var handler = Delegate.CreateDelegate(
eventInfo.EventHandlerType,
this,
nameof(HandlePoseLandmarksOutput)
);
eventInfo.RemoveEventHandler(solution, handler);
}
catch (Exception ex)
{
Debug.LogWarning($"取消订阅时出错: {ex.Message}");
}
}
}
// 销毁调试UI
if (debugCanvasInstance != null)
{
Destroy(debugCanvasInstance);
}
}
}
```
### 关键修复点:
1. **坐标转换修复**:
```csharp
// 将归一化坐标转换为屏幕坐标
Vector3 hipScreen = new Vector3(hip.X * Screen.width, (1 - hip.Y) * Screen.height, 0);
// 转换为世界坐标(在摄像机前方10单位处)
debugHipPos = mainCamera.ScreenToWorldPoint(new Vector3(hipScreen.x, hipScreen.y, 10f));
```
2. **添加游戏内调试UI**:
```csharp
public GameObject debugCanvasPrefab; // 调试UI预制体
private GameObject debugCanvasInstance;
private Text debugText;
void Start()
{
// 创建调试UI
if (showInGameDebug && debugCanvasPrefab != null)
{
debugCanvasInstance = Instantiate(debugCanvasPrefab);
debugText = debugCanvasInstance.GetComponentInChildren<Text>();
}
}
```
3. **增强Gizmos可视化**:
```csharp
// 绘制标签
GUIStyle style = new GUIStyle();
style.normal.textColor = Color.white;
style.fontSize = 12;
UnityEditor.Handles.Label(debugHipPos, "髋关节", style);
```
### 创建调试UI预制体(必须步骤)
1. **在Unity编辑器中**:
- 创建新Canvas:`GameObject > UI > Canvas`
- 添加Text组件:`GameObject > UI > Text`
- 设置Text属性:
- 锚点:左上角
- 位置:(50, -50, 0)
- 字体大小:24
- 颜色:白色
- 将Canvas保存为预制体:"DebugCanvas"
- 在Inspector中将预制体拖拽到KickDetector的`debugCanvasPrefab`字段
### 常见问题解决方案:
1. **Gizmos不可见**:
- 在Scene视图点击Gizmos按钮确保开启
- 确保`debugVisualization`在Inspector中勾选
- 检查坐标转换是否正确(使用ScreenToWorldPoint)
2. **坐标显示不正确**:
- 确认主摄像机已设置(Camera.main)
- 尝试调整ScreenToWorldPoint的z值(10f)
- 添加日志输出坐标值:`Debug.Log($"Hip: {debugHipPos}");`
3. **备选调试方法(如果Gizmos仍不可见)**:
```csharp
// 在Update中添加
void Update()
{
if (debugVisualization)
{
Debug.DrawLine(debugHipPos, debugKneePos, Color.red);
Debug.DrawLine(debugKneePos, debugAnklePos, Color.red);
DebugDrawSphere(debugHipPos, 0.1f, Color.green);
DebugDrawSphere(debugKneePos, 0.1f, Color.green);
DebugDrawSphere(debugAnklePos, 0.1f, Color.green);
}
}
void DebugDrawSphere(Vector3 position, float radius, Color color)
{
int segments = 12;
float angle = 0f;
Vector3 lastPoint = position + new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * radius;
for (int i = 1; i <= segments; i++)
{
angle = (i / (float)segments) * Mathf.PI * 2;
Vector3 nextPoint = position + new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * radius;
Debug.DrawLine(lastPoint, nextPoint, color);
lastPoint = nextPoint;
}
}
```
4. **编辑器专用代码处理**:
```csharp
#if UNITY_EDITOR
// 绘制标签
UnityEditor.Handles.Label(debugHipPos, "髋关节", style);
#endif
```
### 最终检查清单:
1. 确保主摄像机已设置且启用
2. 在Inspector中勾选`debugVisualization`
3. 将调试UI预制体拖拽到脚本的`debugCanvasPrefab`字段
4. 在Scene视图中开启Gizmos显示
5. 调整`debugScale`值使可视化大小合适