【问题标题】:Smooth Mouse Movement using mouse_event with set delay C++使用具有设置延迟 C++ 的 mouse_event 平滑鼠标移动
【发布时间】:2020-04-07 11:08:15
【问题描述】:

我正在编写鼠标宏。它需要在每个点之间的设定延迟内满足屏幕上的某些点。例如,它必须在 132 毫秒内移动 (x 14, y 30)。我遇到的问题是 mouse_event 跳到那个确切的位置,所以我需要包含某种平滑方法,以便它平滑地移动到每个点。 (运动越平滑,宏就越好)。目前我正在使用这种平滑每个动作的方法。

这很好用,但它有其局限性,例如,如果它需要向左移动 10 个像素并且平滑设置为 20,它将继续跳跃。

有人知道平滑鼠标移动的更准确方法吗? (要求准确、流畅)

void Smoothing(int smoothing, int delay, int x, int y) {
    for (int i = 0; i < smoothing; i++) {
        mouse_event(1, x / smoothing, y / smoothing, 0, 0);
        AccurateSleep(delay / smoothing);
    }
    mouse_event(1, x % smoothing, y % smoothing, 0, 0);
    Sleep(delay % smoothing);
}


【问题讨论】:

  • 欢迎来到 StackOverflow。您可以查看tour 以获得简短介绍。

标签: c++ macros mouseevent mouse smoothing


【解决方案1】:

Linear Interpolation 是我阅读问题时的第一个想法(以及the other answer 中提到的)。

插值的一般公式是:

    x = (1 - t) · x0 + t · x1

    x ...插值
x0 ... 起始值
x1 ...目标值
t ... [0, 1] 范围内的插值参数

当我意识到一些可能形成可能限制的事实(不幸的是,OP 没有明确提及)时,我什至打算写这个作为答案。

  1. 所有操作都是关于整数值。因此,可能首选进行整数运算。
  2. mouse_event()AccurateSleep() 使用 delta 值调用。这可能由 OP 使用的 API 决定。

所以,我三思而后行,并提出了以下MCVE 类似于 OPs 问题:

#include <iostream>

static int xMouse = 0, yMouse = 0, t = 0;

void mouse_event(int _1, int dx, int dy, int _4, int _5)
{
  xMouse += dx; yMouse += dy;
  std::cout << "mouse_event(" << _1 << ", " << dx << ", " << dy << ", " << _4 << ", " << _5 << "): "
    << xMouse << ", " << yMouse << '\n';
}

void AccurateSleep(int delay)
{
  t += delay;
  std::cout << "AccurateSleep(" << delay << "): " << t << '\n';

}

void Sleep(int delay)
{
  t += delay;
  std::cout << "Sleep(" << delay << "): " << t << '\n';
}

void Smoothing(int smoothing, int delay, int x, int y)
{
    for (int i = 0; i < smoothing; i++) {
        mouse_event(1, x / smoothing, y / smoothing, 0, 0);
        AccurateSleep(delay / smoothing);
    }
    mouse_event(1, x % smoothing, y % smoothing, 0, 0);
    Sleep(delay % smoothing);
}

#define PRINT_AND_DO(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ 

int main()
{
  PRINT_AND_DO(xMouse = 0; yMouse = 0; t = 0);
  PRINT_AND_DO(Smoothing(10, 132, 14, 30));
  PRINT_AND_DO(xMouse = 0; yMouse = 0; t = 0);
  PRINT_AND_DO(Smoothing(20, 15, 10, 0));
}

输出:

xMouse = 0; yMouse = 0; t = 0;
Smoothing(10, 132, 14, 30);
mouse_event(1, 1, 3, 0, 0): 1, 3
AccurateSleep(13): 13
mouse_event(1, 1, 3, 0, 0): 2, 6
AccurateSleep(13): 26
mouse_event(1, 1, 3, 0, 0): 3, 9
AccurateSleep(13): 39
mouse_event(1, 1, 3, 0, 0): 4, 12
AccurateSleep(13): 52
mouse_event(1, 1, 3, 0, 0): 5, 15
AccurateSleep(13): 65
mouse_event(1, 1, 3, 0, 0): 6, 18
AccurateSleep(13): 78
mouse_event(1, 1, 3, 0, 0): 7, 21
AccurateSleep(13): 91
mouse_event(1, 1, 3, 0, 0): 8, 24
AccurateSleep(13): 104
mouse_event(1, 1, 3, 0, 0): 9, 27
AccurateSleep(13): 117
mouse_event(1, 1, 3, 0, 0): 10, 30
AccurateSleep(13): 130
mouse_event(1, 4, 0, 0, 0): 14, 30
Sleep(2): 132

xMouse = 0; yMouse = 0; t = 0;
Smoothing(20, 15, 10, 0);
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 10, 0, 0, 0): 10, 0
Sleep(15): 15

然后我修改Smoothing()实现上面提到的插值公式,并针对具体情况做了一些调整:

  1. 对于t,使用i / smoothingi 在 [1,平滑] 范围内)。
  2. 虽然循环对每个 i 进行插值,但前一次迭代的值被保留并用于计算 mouse_event()AccurateSleep() 的函数调用的增量值。
  3. 当然,运算顺序很重要,因为这是整数运算。因此,xI = i * x / smoothing 不等于 xI = i / smoothing * x。 (即这些积分运算不提供交换性。)

修改后的Smoothing()

void Smoothing(int smoothing, int delay, int x, int y)
{
  int x_ = 0, y_ = 0, t_ = 0;
  for (int i = 1; i <= smoothing; ++i) {
    // i / smoothing provides the interpolation paramter in [0, 1]
    int xI = i * x / smoothing;
    int yI = i * y / smoothing;
    int tI = i * delay / smoothing;
    mouse_event(1, xI - x_, yI - y_, 0, 0);
    AccurateSleep(tI - t_);
    x_ = xI; y_ = yI; t_ = tI;
  }
}

输出:

xMouse = 0; yMouse = 0; t = 0;
Smoothing(10, 132, 14, 30);
mouse_event(1, 1, 3, 0, 0): 1, 3
AccurateSleep(13): 13
mouse_event(1, 1, 3, 0, 0): 2, 6
AccurateSleep(13): 26
mouse_event(1, 2, 3, 0, 0): 4, 9
AccurateSleep(13): 39
mouse_event(1, 1, 3, 0, 0): 5, 12
AccurateSleep(13): 52
mouse_event(1, 2, 3, 0, 0): 7, 15
AccurateSleep(14): 66
mouse_event(1, 1, 3, 0, 0): 8, 18
AccurateSleep(13): 79
mouse_event(1, 1, 3, 0, 0): 9, 21
AccurateSleep(13): 92
mouse_event(1, 2, 3, 0, 0): 11, 24
AccurateSleep(13): 105
mouse_event(1, 1, 3, 0, 0): 12, 27
AccurateSleep(13): 118
mouse_event(1, 2, 3, 0, 0): 14, 30
AccurateSleep(14): 132

xMouse = 0; yMouse = 0; t = 0;
Smoothing(20, 15, 10, 0);
mouse_event(1, 0, 0, 0, 0): 0, 0
AccurateSleep(0): 0
mouse_event(1, 1, 0, 0, 0): 1, 0
AccurateSleep(1): 1
mouse_event(1, 0, 0, 0, 0): 1, 0
AccurateSleep(1): 2
mouse_event(1, 1, 0, 0, 0): 2, 0
AccurateSleep(1): 3
mouse_event(1, 0, 0, 0, 0): 2, 0
AccurateSleep(0): 3
mouse_event(1, 1, 0, 0, 0): 3, 0
AccurateSleep(1): 4
mouse_event(1, 0, 0, 0, 0): 3, 0
AccurateSleep(1): 5
mouse_event(1, 1, 0, 0, 0): 4, 0
AccurateSleep(1): 6
mouse_event(1, 0, 0, 0, 0): 4, 0
AccurateSleep(0): 6
mouse_event(1, 1, 0, 0, 0): 5, 0
AccurateSleep(1): 7
mouse_event(1, 0, 0, 0, 0): 5, 0
AccurateSleep(1): 8
mouse_event(1, 1, 0, 0, 0): 6, 0
AccurateSleep(1): 9
mouse_event(1, 0, 0, 0, 0): 6, 0
AccurateSleep(0): 9
mouse_event(1, 1, 0, 0, 0): 7, 0
AccurateSleep(1): 10
mouse_event(1, 0, 0, 0, 0): 7, 0
AccurateSleep(1): 11
mouse_event(1, 1, 0, 0, 0): 8, 0
AccurateSleep(1): 12
mouse_event(1, 0, 0, 0, 0): 8, 0
AccurateSleep(0): 12
mouse_event(1, 1, 0, 0, 0): 9, 0
AccurateSleep(1): 13
mouse_event(1, 0, 0, 0, 0): 9, 0
AccurateSleep(1): 14
mouse_event(1, 1, 0, 0, 0): 10, 0
AccurateSleep(1): 15

Live Demo on coliru

注意:

最后一次迭代是用i == smoothing 完成的,所以i / smoothing 的结果是 1。因此,最后一个插值步骤会产生精确的值——不需要像 OP 原始方法那样进行后校正。

【讨论】:

    【解决方案2】:

    将点视为向量并在它们之间进行插值。 这通常被称为线性插值的“lerping”排序。 如果您搜索线性插值,您可以找到许多可能有帮助的资源。 这是an answer,可能有助于理解它是什么。

    由于我手头有额外的时间,所以我输入了一个程序示例,该程序也能做到这一点。

    #include <iostream>
    #include <chrono>
    
    struct Vec2d {
        double x;
        double y;
        Vec2d(double x, double y) : x(x), y(y) {};
    };
    
    Vec2d lerp(Vec2d const& a, Vec2d const& b, double t)  {
        double x((1.0 - t) * a.x + t * b.x);
        double y((1.0 - t) * a.y + t * b.y);
        return Vec2d(x, y);
    }
    
    int main(int argc, char* argv[]) {
        Vec2d p1(10, 10);
        Vec2d p2(20, 40);
    
        double maxTime(100); //max time 100 milliseconds
        double elapsedTime(0);
        std::chrono::time_point<std::chrono::system_clock> start(std::chrono::system_clock::now());
        std::chrono::time_point<std::chrono::system_clock> end(start);
        while(elapsedTime < maxTime) {
            elapsedTime += std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
            start = end;
    
            //This is where the lerping happens
            double t(elapsedTime / maxTime);
            Vec2d p3(lerp(p1, p2, t));
    
            //Show what's happening.
            std::cout << "p3: " << p3.x << ", " << p3.y << std::endl;
            end = std::chrono::system_clock::now();
        }
    
        return 0;
    }
    

    简短说明: t 是从 0 到 1 的值。 当t == 0.0 lerp 将返回p1 的“副本”。 当t == 1.0 lerp 将返回p2 的“副本”。 当t == 0.5 lerp 将返回(p1 + p2) / 2(它们之间的中点)。

    您还需要添加代码以不断更新鼠标的位置。 为此,您需要跟踪已经过去了多少时间,并根据从p1p2 所需的时间和实际过去的时间计算t 的值。上面的代码使用 while 循环和std::chrono 来跟踪经过的时间。然而,这将取决于您打算如何触发这些“更新”。

    希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-06
      • 1970-01-01
      相关资源
      最近更新 更多