模拟持久按键

Simulate lasting key press

本文关键字:模拟      更新时间:2023-10-16

在我的程序中,我想模拟按键。例如,我想说 Press(KeyA, 1000(,其中第一个参数是 KeyCode,第二个参数是以毫塞孔为单位的时间。KeyA 等是 typedef,这些方法非常适合只需按下一次按钮即可。

void PressKey(INPUT &ip, int keycode, int ms)
{
// Press the  key
ip.ki.wVk = keycode; // virtual-key code for the key
ip.ki.dwFlags = 0; // 0 for key press
SendInput(1, &ip, sizeof(INPUT));
sleep(ms);
// Release the  key
ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
SendInput(1, &ip, sizeof(INPUT));
}
int main()
{

// input event
INPUT ip;
INPUT ipMouse;
ip.type = INPUT_KEYBOARD;
ip.ki.wScan = 0; // hardware scan code for key
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;

PressKey(ip, KeyM, 1000);

我以为按下键,然后等到松开它会算作按住按钮,但事实并非如此。我也不想只说一千次 PressKey(ip, KeyM( 以获得与按住按钮 5 秒钟相同的输入。现在有人如何使程序模拟更长的输入吗?

我将回答更一般的问题"如何使用特定输入重播我的游戏运行?

重播输入、用于分析、检查确定性或用于一般 QA 的诀窍是从游戏处理中分离两个元素:

  • 对计时器的任何访问
  • 从播放器读取任何输入

如果您的游戏编写良好(没有未定义的行为(,并且具有一组给定的输入和关联的计时,则它的行为应始终相同。同时相同的输入会产生相同的输出。

因此,将 TimeGetTime((、std::clock、rdtsc 或任何你用来读取时间的内容包装到你自己的 getTime(( 函数中。同样,将 win32 输入读取和/或扫描码读取包装到您自己的 getInputs(( 中。

运行游戏,但除了使用它们之外,还要将所有输入和时间戳保存到文件中。日志.txt什么的。理想情况下,您的游戏应该每帧只询问一次时间,但是,嘿,我见过每帧查询数千次当前时间的游戏。

现在,修改游戏,使其不是获取实际输入或真实挂钟,而是从日志文件中获取值。如果您的游戏是确定性的,您现在应该能够重播相同的事件序列。

然后,您可以对其进行分析。请注意,由于时间是从文件中读取的,如果游戏在重播时碰巧变慢,则不是问题。你的角色会跳跃相同的长度,落在同一个地方,等等......它只会看起来更慢。这允许您使用最密集的配置文件设置,即使游戏玩得慢 10 倍,它仍然完全相同,您可以有效地测量花费时间的位置。

它还为您准备了 QA 可以保存重播并可以调试的设置。

记录所有这些工作更多,需要注意不要有任何非确定性。

但是,另一方面,如果您遵循原始建议,则输入注入不会完全同时发生。您将失去一些 QA 重现功能。从运行到运行,行为会有所不同。

探查器开销也会影响计时,使测量有时无效。

所以,我的建议:

  • 制作一个接近确定性的游戏(很可能总是会有轻微的差异(
  • 将所有输入(包括时间(包装在日志记录函数后面
  • 将它们全部保存以记录
  • 将它们替换为记录的历史记录以重播

具体解决问题的框架: 游戏不是通过 win32 系统提供输入,而是调用您自己的 getInput((,并在重播时获取存储的输入,从而绕过整个 win32 系统。

但是,没有这样的 API 来模拟按键按住。

测试窗口应用程序时,当您按住键盘上的键时,它将收到连续WM_KEYDOWN消息。

如果你的应用程序是窗口应用程序而不是控制台应用程序,则可以检测低级键盘挂钩中的第一个WM_KEYDOWN并启动一个 5 秒的计时器,当计时器超时时,发送一条WM_KEYUP消息,以便模拟按住。您只能呼叫SendInput一次并发送一条按键消息。

对于控制台应用程序,它无法挂钩,在指定的时间内重复调用 SendInput 是适合您的解决方案。对此有什么顾虑吗?