对于GetAsyncKeyState()函数实现贴图移动的问题

最近在做类似于飞机大战的小游戏,其中就有对于飞机移动的实现,为了按键移动更流畅,都是用的GetAsyncKeyState()函数,而不是getch()来获取按键。但我自己在编程的时候遇到移动过于灵敏的情况,找不出原因,测试代码如下:

#include 
#include 

struct gamer {
    int x;
    int y;
};
struct gamer gamer1;
IMAGE img;

void move() {
    if (GetAsyncKeyState(VK_UP)) {
        gamer1.y -=10;

    }
    if (GetAsyncKeyState(VK_DOWN)) {
        gamer1.y +=10;

    }
}
int main() {
    initgraph(640, 480);
    gamer1.x = 0;
    gamer1.y = 0;
    loadimage(&img,"dog.jpg");
    while (1) {
        putimage(gamer1.x, gamer1.y, &img);
        move();
        //_getch();
        cleardevice();
    }
    return 0;
}

运行的时候发现按一下方向键,贴图会移动的飞快(这段代码中没有编写边界保护),即使把贴图移动速度设置的很慢也解决不了问题。
为什么会出现这样的情况?

仅供参考:

#include <conio.h>
#include <windows.h>

void ConPrintAt(int x, int y, char *CharBuffer, int len)
{
   DWORD count;
   COORD coord = {x, y};
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   SetConsoleCursorPosition(hStdOut, coord);
   WriteConsole(hStdOut, CharBuffer, len, &count, NULL);
}
void HideTheCursor()
{
   CONSOLE_CURSOR_INFO cciCursor;
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

   if(GetConsoleCursorInfo(hStdOut, &cciCursor))
   {
      cciCursor.bVisible = FALSE;
      SetConsoleCursorInfo(hStdOut, &cciCursor);
   }
}

void ShowTheCursor()
{
   CONSOLE_CURSOR_INFO cciCursor;
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

   if(GetConsoleCursorInfo(hStdOut, &cciCursor))
   {
      cciCursor.bVisible = TRUE;
      SetConsoleCursorInfo(hStdOut, &cciCursor);
   }
}
void GetWH(int *w,int *h) {
    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if (GetConsoleScreenBufferInfo(hStdOut, &csbi)) {
        *w=csbi.srWindow.Right;
        *h=csbi.srWindow.Bottom;
    } else {
        *w=80;
        *h=25;
    }
}
void ClearConsole()
{
   //Get the handle to the current output buffer...
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   //This is used to reset the carat/cursor to the top left.
   COORD coord = {0, 0};
   //A return value... indicating how many chars were written
   //   not used but we need to capture this since it will be
   //   written anyway (passing NULL causes an access violation).
   DWORD count;
   //This is a structure containing all of the console info
   // it is used here to find the size of the console.
   CONSOLE_SCREEN_BUFFER_INFO csbi;
   //Here we will set the current color
   if(GetConsoleScreenBufferInfo(hStdOut, &csbi))
   {
      //This fills the buffer with a given character (in this case 32=space).
      FillConsoleOutputCharacter(hStdOut, (TCHAR) 32, csbi.dwSize.X * csbi.dwSize.Y, coord, &count);
      FillConsoleOutputAttribute(hStdOut, csbi.wAttributes, csbi.dwSize.X * csbi.dwSize.Y, coord, &count);
      //This will set our cursor position for the next print statement.
      SetConsoleCursorPosition(hStdOut, coord);
   }
}

int main() {
    unsigned short k;
    int x,y,w,h;

    SetConsoleOutputCP(437);
    ClearConsole();
    GetWH(&w,&h);
    x=w/2;y=h/2;
    HideTheCursor();
    ConPrintAt(x,y,"O",1);
    while (1) {
        Sleep(50);
        k=getch();
        if (27==k) break;//按Esc键退出
        if (0==k||0xe0==k) k|=getch()<<8;//非字符键
        switch (k) {
            case 0x48e0:case 0x04800://if (y>0) {
                    ConPrintAt(x,y," ",1);
                    y--;
                    ConPrintAt(x,y,"O",1);
                }
            break;
            case 0x50e0:case 0x05000://if (y<h) {
                    ConPrintAt(x,y," ",1);
                    y++;
                    ConPrintAt(x,y,"O",1);
                }
            break;
            case 0x4be0:case 0x04b00://if (x>0) {
                    ConPrintAt(x,y," ",1);
                    x--;
                    ConPrintAt(x,y,"O",1);
                }
            break;
            case 0x4de0:case 0x04d00://if (x<w-1) {
                    ConPrintAt(x,y," ",1);
                    x++;
                    ConPrintAt(x,y,"O",1);
                }
            break;
        }
//      cprintf("%04x pressed.\r\n",k);

    }
    ClearConsole();
    ShowTheCursor();
    return 0;
}



不要简单地检测GetAsyncKeyState的返回值是否非零,因为如果在这个函数运行前指定的键已经被按过,则这个函数的返回值的位0为1;如果这个键目前处于按下状态,则位15为1。所以你检测位15是否为1就可以知道这个键是曾经被按过还是正在被按着:
If (GetAsyncKeyState(VK_SPACE) And &H8000) = &H8000 Then Call 事件A
还有一个API函数GetKeyState是只检测按键的当前状态的,但仍然要检测位15是否为1(位0为1表示开关键打开,即CapsLock、NumLock、ScrollLock键):
If (GetKeyState(VK_SPACE) And &H8000) = &H8000 Then Call 事件A

更多解释可以参考一下这个:
https://blog.csdn.net/weixin_39982933/article/details/117130606

判断要加上 0x8000,如:
if (GetAsyncKeyState(VK_UP) && 0x8000) {
gamer1.y -=10;
}