删除 KeyDown 事件后的延迟?

Removing the delay after KeyDown event?

当我在游戏中按住一个键来移动我的玩家时:

public void MainForm_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Up)
    {
        Player.MoveUp();
    }
}

我一按向下箭头,播放器立即移动了一步,然后停顿了一小会儿,然后又开始平稳移动了。这是为什么?我该如何预防?

不幸的是,the proposed duplicate 中的答案不正确。它不会忽略重复的 KeyDown 事件,因此会在每个关键案例处理的方向上逐渐增加 "delta" 值。它也不会立即响应按键(即在第一个计时器滴答之前不会发生任何操作)。

这个对 Holding Arrow Keys Down For Character Movement C# .Net ISSUES 的回答解释了如何忽略后续的 KeyDown 事件,但没有解释你的角色将如何移动。

换句话说,我找不到实际上正确 回答您问题的重复问题。所以……

您想做的基本技术是:

  1. 不要移动实际的键输入。相反,生成您自己的将移动对象的时序逻辑。
  2. 不是使用 KeyDown 事件来实际移动对象,而是使用它来设置移动方向,然后由您的计时逻辑处理。

有多种方法可以实现这一点。一个版本看起来像这样:

private bool _moveUp;
private bool _moveDown;
private bool _moveLeft;
private bool _moveRight;

// You can add the Timer in the Winforms Designer instead if you like;
// The Interval property can be configured there at the same time, along
// with the Tick event handler, simplifying the non-Designer code here.
private System.Windows.Forms.Timer _movementTimer = new Timer { Interval = 100 };

public MainForm()
{
    InitializeComponent();

    _movementTimer.Tick += movementTimer_Tick;
}

private void movementTimer_Tick(object sender, EventArgs e)
{
    _DoMovement();
}

private void _DoMovement()
{
    if (_moveLeft) Player.MoveLeft();
    if (_moveRight) Player.MoveRight();
    if (_moveUp) Player.MoveUp();
    if (_moveDown) Player.MoveDown();
}

// You could of course override the OnKeyDown() method instead,
// assuming the handler is in the Form subclass generating the
// the event.
public void MainForm_KeyDown(object sender, KeyEventArgs e)
{
    if (e.IsRepeat)
    {
        // Ignore key repeats...let the timer handle that
        return;
    }

    switch (e.KeyCode)
    {
    case Keys.Up:
        _moveUp = true;
        break;
    case Keys.Down:
        _moveDown = true;
        break;
    case Keys.Left:
        _moveLeft = true;
        break;
    case Keys.Right:
        _moveRight = true;
        break;
    }

    _DoMovement();
    _movementTimer.Start();
}

public void MainForm_KeyUp(object sender, KeyEventArgs e)
{
    switch (e.KeyCode)
    {
    case Keys.Up:
        _moveUp = false;
        break;
    case Keys.Down:
        _moveDown = false;
        break;
    case Keys.Left:
        _moveLeft = false;
        break;
    case Keys.Right:
        _moveRight = false;
        break;
    }

    if (!(_moveUp || _moveDown || _moveLeft || _moveRight))
    {
        _movementTimer.Stop();
    }
}

请注意 .NET 中的计时器对象的分辨率有限。我在上面显示了 100 毫秒(每秒 10 次)的间隔(与其他问题的答案相同),这与您将可靠地获得的更新频率差不多。即便如此,计时器的 Tick 事件可能不会(也可能不会)在 恰好 100 毫秒间隔上引发。来回会有一些变化。但对于基本游戏来说已经足够接近了。

如果您需要比这更高的精度,则必须在某处实现您自己的状态轮询和动画循环。那完全是另一个蜡球。 :)

一种主观优雅的方法:

public partial class Form1 : Form
{
    private static Timer timer;
    private static bool[] keys_down;
    private static Keys[] key_props;

    private void Form1_Load(object sender, EventArgs e)
    {
        keys_down = new bool[4];
        key_props = new []{Keys.A, Keys.D, Keys.W, Keys.S};
        timer = new Timer();
        timer.Interval = 15; // Roughly 67 FPS
        timer.Tick += tick;
        timer.Start();
        KeyDown += key_down_event;
        KeyUp += key_up_event;
        ... // More things to do when the form loads.
    }

    private void tick(Object source, EventArgs e)
    {
        ... // Do this every timing interval.
        byte n = 0;
        foreach (var v in keys_down)
        {
            if (n == 3 && v)
                ... // If the "s" key is being held down, no key delay issues. :)
            n++;
        }
        ...
    }

    private void key_down_event(object sender, KeyEventArgs e)
    {
        byte n = 0;
        foreach (var v in keys_down)
        {
            if (e.KeyCode == key_props[n])
                keys_down[n] = true;
            n++;
        }
    }

    private void key_up_event(object sender, KeyEventArgs e)
    {
        byte n = 0;
        foreach (var v in keys_down)
        {
            if (e.KeyCode == key_props[n])
                keys_down[n] = false;
            n++;
        }
    }

    public Form1()
    {
        InitializeComponent();
    }
}

我正在寻找创建 Flappy Bird 小副本的解决方案。也许我的决定会对某人有所帮助。 我在计时器中添加了一个带有变量的玩家位置来模拟重力。当我按下 W 时,我使用 bool 关闭了对命令的进一步接收,并简单地反转了 gravity

private void time_Tick(object sender, EventArgs e)
    {
        if (bird.Location.Y >= 540)
        {
            bird.Location = new Point(bird.Location.X, 540);
        }
        else 
        {
            bird.Location = new Point(bird.Location.X, bird.Location.Y+grav);
        } 
    }

private void Form1_KeyPress(object sender, KeyEventArgs e)
    {
        if (e.KeyData == Keys.W && press == false)
        {
            press = true;
            grav = -10;
        }
        return;
    }

    private void Form1_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.KeyData == Keys.W && press == true)
        {
            press = false;
            grav = 5;
        }
        return;
    }