You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 00:11:00