删除 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
事件,但没有解释你的角色将如何移动。
换句话说,我找不到实际上正确 回答您问题的重复问题。所以……
您想做的基本技术是:
- 不要移动实际的键输入。相反,生成您自己的将移动对象的时序逻辑。
- 不是使用
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;
}
当我在游戏中按住一个键来移动我的玩家时:
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
事件,但没有解释你的角色将如何移动。
换句话说,我找不到实际上正确 回答您问题的重复问题。所以……
您想做的基本技术是:
- 不要移动实际的键输入。相反,生成您自己的将移动对象的时序逻辑。
- 不是使用
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;
}