You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Expo移动端控制PC鼠标出现卡顿跳帧的问题求助

Expo移动端控制PC鼠标出现卡顿跳帧的问题求助

看起来你已经搭好了Expo移动端到C#服务器的鼠标控制链路,但遇到了卡顿跳帧的糟心事,我帮你排查了代码里的几个关键问题,应该能解决这个情况:

一、JavaScript端节流逻辑的致命疏漏

看你的Pan手势onUpdate代码里的节流判断:你设置了now - lastSentTime > 16来限制60fps的发送频率,但完全没在发送消息后更新lastSentTime的值

这会导致节流彻底失效:第一次满足条件发完消息后,lastSentTime还是初始值,之后每次onUpdate都会触发发送(因为时间差永远大于16ms),直接导致WebSocket消息暴增,服务器处理不过来、消息堆积,这绝对是卡顿跳帧的核心原因之一。

修复方法很简单,在发送消息的代码块里加上lastSentTime = now;

const now = Date.now();
if (now - lastSentTime > 16) { // ~60fps throttle
  if (onMove) runOnJS(onMove)(dx, dy);
  lastSentTime = now; // 必须加上这行更新时间戳!
}

另外要确保lastSentTime有初始值(比如在变量声明时设为let lastSentTime = 0;)。

二、Windows鼠标API的单位误解(大坑)

你的C#代码用SendInputMOUSEEVENTF_MOVE来移动鼠标,但这里有个很容易踩的坑:MOUSEEVENTF_MOVEdx/dy参数不是屏幕像素,而是Windows原生的“Mickeys”单位(1 Mickey = 1/200英寸,和屏幕分辨率无关)。直接传像素值会导致实际移动距离和预期完全不符,表现为鼠标跳来跳去、移动不跟手。

这里给你两个修复方案,选一个就行:

方案1:像素转Mickeys(相对移动)

Windows里通常1像素≈2 Mickeys,你可以按这个比例转换,也可以根据系统鼠标速度调整:

private void MoveMouseBy(int dx, int dy)
{
    // 把像素位移转成Mickeys,比例可根据实际手感调整
    int mickeyDx = dx * 2;
    int mickeyDy = dy * 2;

    INPUT[] inputs = new INPUT[1];
    inputs[0].type = INPUT_MOUSE;
    inputs[0].mi.dx = mickeyDx;
    inputs[0].mi.dy = mickeyDy;
    inputs[0].mi.mouseData = 0;
    inputs[0].mi.dwFlags = MOUSEEVENTF_MOVE; // 相对移动不需要ABSOLUTE标志
    inputs[0].mi.time = 0;
    inputs[0].mi.dwExtraInfo = IntPtr.Zero;

    uint result = SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT)));
    if (result == 0)
    {
        Console.WriteLine("SendInput failed with error: " + Marshal.GetLastWin32Error());
    }
}

方案2:绝对位置移动(更精准)

获取当前鼠标位置,加上位移后转换成Windows标准的绝对坐标范围(0到65535),用MOUSEEVENTF_ABSOLUTE标志发送,这种方式手感会更跟手:

private void MoveMouseToAbsolute(int x, int y)
{
    int screenWidth = Screen.PrimaryScreen.Bounds.Width;
    int screenHeight = Screen.PrimaryScreen.Bounds.Height;

    // 把屏幕像素坐标转成Windows绝对坐标(范围0-65535)
    int absoluteX = (x * 65535) / screenWidth;
    int absoluteY = (y * 65535) / screenHeight;

    INPUT[] inputs = new INPUT[1];
    inputs[0].type = INPUT_MOUSE;
    inputs[0].mi.dx = absoluteX;
    inputs[0].mi.dy = absoluteY;
    inputs[0].mi.mouseData = 0;
    inputs[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
    inputs[0].mi.time = 0;
    inputs[0].mi.dwExtraInfo = IntPtr.Zero;

    uint result = SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT)));
    if (result == 0)
    {
        Console.WriteLine("SendInput failed with error: " + Marshal.GetLastWin32Error());
    }
}

// 然后修改MoveMouseRelative方法,改用绝对位置移动:
public bool MoveMouseRelative(double dx, double dy, MousePadSize padSize)
{
    double mobileWidth = padSize?.Width ?? 300;
    double mobileHeight = padSize?.Height ?? 500;
    int screenWidth = Screen.PrimaryScreen.Bounds.Width;
    int screenHeight = Screen.PrimaryScreen.Bounds.Height;

    double scaleX = (double)screenWidth / mobileWidth;
    double scaleY = (double)screenHeight / mobileHeight;
    double scaledDx = dx * scaleX;
    double scaledDy = dy * scaleY;

    accumX += scaledDx * sensitivity;
    accumY += scaledDy * sensitivity;

    int moveX = (int)Math.Round(accumX);
    int moveY = (int)Math.Round(accumY);

    if (moveX == 0 && moveY == 0) return true;

    accumX -= moveX;
    accumY -= moveY;

    // 获取当前鼠标位置,计算新位置并限制在屏幕范围内
    Point currentPos = Cursor.Position;
    int newX = Math.Clamp(currentPos.X + moveX, 0, screenWidth - 1);
    int newY = Math.Clamp(currentPos.Y + moveY, 0, screenHeight - 1);

    MoveMouseToAbsolute(newX, newY);
    Console.WriteLine($"Moving mouse to: ({newX},{newY})");
    return true;
}

三、其他小优化建议

  1. JS端坐标精度:尽量用e.absoluteX/e.absoluteY替代e.x/e.y,避免组件内坐标的偏移问题,让位移计算更精准。
  2. 服务器端异步处理:如果WebSocket消息还是有点多,可以在C#端加个小型的异步消息队列,把鼠标移动请求放到队列里异步处理,避免阻塞主线程导致的延迟。
  3. 关闭手势惯性:你已经注释掉了onEnd里的惯性代码,这个做得很对——鼠标控制不需要触摸手势的惯性,保持立即停止的逻辑会更跟手。

内容来源于stack exchange

火山引擎 最新活动