Unity中通过手部坐标控制3D角色移动失败问题求助
问题排查与修复方案
1. Python端:发送过多无效坐标(每个手部关键点重复发送)
你的Python代码会遍历每一个手部关节并发送坐标,导致Unity每秒收到上百条混乱的坐标数据,无法确定用哪个点控制角色。需改为只发送单个固定关键点(比如手腕、食指尖)的坐标,同时对坐标做归一化处理,适配Unity世界坐标范围。
修改后的Python代码:
import socket import cv2 import mediapipe as mp mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles mphands = mp.solutions.hands UDP_IP = "127.0.0.1" UDP_PORT = 5005 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) cap = cv2.VideoCapture(0) # 固定摄像头分辨率,方便统一坐标计算 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) with mphands.Hands( model_complexity=0, min_detection_confidence=0.5, min_tracking_confidence=0.5) as hands: while cap.isOpened(): success, image = cap.read() if not success: continue image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) image.flags.writeable = False results = hands.process(image) image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) if results.multi_hand_landmarks: for hand_landmarks in results.multi_hand_landmarks: # 只发送手腕关键点(索引0)的坐标 landmark = hand_landmarks.landmark[0] h, w, c = image.shape # 归一化到0-1范围,方便Unity转换 norm_x = landmark.x norm_y = landmark.y message = f"{norm_x},{norm_y}".encode() sock.sendto(message, (UDP_IP, UDP_PORT)) mp_drawing.draw_landmarks( image, hand_landmarks, mphands.HAND_CONNECTIONS, mp_drawing_styles.get_default_hand_landmarks_style(), mp_drawing_styles.get_default_hand_connections_style()) cv2.imshow('Handtracker', image) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
2. Unity端:线程安全问题(子线程修改主线程变量)
ReceiveData运行在UDP子线程中,直接修改targetPosition会导致主线程(Update读取时)出现数据竞争,引发坐标异常或不更新。需将变量更新逻辑切换到主线程执行。
3. Unity端:坐标范围不匹配
摄像头像素坐标与Unity世界坐标范围差异极大,直接使用原始值会让角色瞬间跑到场景外,看起来像没移动。需将归一化坐标转换为场景内的合理移动范围。
修改后的Unity代码:
using UnityEngine; using System.Net; using System.Net.Sockets; using System; using System.Collections.Generic; public class PlayerBehavior : MonoBehaviour { public float movementSpeed = 10f; // 自定义角色在X/Y轴的移动范围 public float maxX = 5f; public float maxY = 3f; private UdpClient udpClient; private int port = 5005; private Vector3 targetPosition; private Queue<Action> mainThreadActions = new Queue<Action>(); void Start() { udpClient = new UdpClient(port); udpClient.BeginReceive(ReceiveData, null); targetPosition = transform.position; } private void ReceiveData(IAsyncResult result) { IPEndPoint source = new IPEndPoint(IPAddress.Any, 0); byte[] receivedBytes = udpClient.EndReceive(result, ref source); string receivedText = System.Text.Encoding.ASCII.GetString(receivedBytes); string[] coordinates = receivedText.Split(','); if (coordinates.Length == 2) { if (float.TryParse(coordinates[0], out float normX) && float.TryParse(coordinates[1], out float normY)) { // 转换为Unity世界坐标,翻转Y轴(摄像头Y轴与Unity方向相反) float worldX = Mathf.Lerp(-maxX, maxX, normX); float worldY = Mathf.Lerp(maxY, -maxY, normY); // 将更新逻辑加入主线程队列 lock (mainThreadActions) { mainThreadActions.Enqueue(() => { targetPosition = new Vector3(worldX, worldY, transform.position.z); Debug.Log($"Target position: {targetPosition}"); }); } } } udpClient.BeginReceive(ReceiveData, null); } void Update() { // 执行主线程队列中的逻辑 lock (mainThreadActions) { while (mainThreadActions.Count > 0) { mainThreadActions.Dequeue()(); } } // 平滑移动角色 transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * movementSpeed); } void OnApplicationQuit() { udpClient.Close(); } }
额外验证步骤
- 查看Unity控制台的
Debug.Log,确认输出的是转换后的世界坐标,而非原始像素值 - 调整
movementSpeed、maxX、maxY参数,适配你的场景大小 - 确保Python端仅发送单个关键点的坐标,避免数据拥堵
内容的提问来源于stack exchange,提问作者Gradea Razvan
相关产品推荐
相关产品推荐

