当我在游戏中按住一个键移动我的播放器时:
public void MainForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Up)
{
Player.MoveUp();
}
}
当我按下向下箭头时,玩家立即移动一步,然后暂停一小段时间,然后再开始平稳移动。这是为什么?我该如何预防?
答案 0 :(得分:3)
遗憾的是,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 ms间隔时引发。来回会有一些变化。但它足够接近基本游戏。
如果你需要更高的精度,你必须在某处实现自己的状态轮询和动画循环。那是另一个球。 :)
答案 1 :(得分:0)
一种主观的优雅方法:
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();
}
}
答案 2 :(得分:0)
我一直在寻找解决方案来创建Flappy Bird的小本。也许我的决定会帮助某人。 我使用了在计时器中添加变量的球员位置来模拟重力。当我按下W键时,我使用布尔和简单的反向重力关闭了对命令的进一步接收
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;
}