我正在使用C#WinForms创建一个Space Invaders游戏,当编码玩家大炮的移动时,我创建了这个事件处理程序:
private void Game_Screen_KeyDown(object sender, KeyEventArgs e)
{
for (int i = 0; i < 500; i++)
{
if (e.KeyCode == Keys.Left)
{
cannonBox.Location = new Point(cannonBox.Left - 2, cannonBox.Top); //Changes location of cannonBox to a new location to the left
Application.DoEvents();
System.Threading.Thread.Sleep(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Right)
{
cannonBox.Location = new Point(cannonBox.Left + 2, cannonBox.Top); //Changes location of cannonBox to a new location to the right
Application.DoEvents();
System.Threading.Thread.Sleep(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Up)
{
createLaser(); //Calls the method whenever Up arrow key is pressed
}
}
}
但是在不同的网站上关于这在C#中是如何不可靠的,我将确保不在其上使用它。还有哪些其他替代方法可以代替此实例中的Application.DoEvents使用?
答案 0 :(得分:5)
我建议制作该事件处理程序async
并使用await Task.Delay()
代替Thread.Sleep()
:
private async void Game_Screen_KeyDown(object sender, KeyEventArgs e)
{
for (int i = 0; i < 500; i++)
{
if (e.KeyCode == Keys.Left)
{
cannonBox.Location = new Point(cannonBox.Left - 2, cannonBox.Top); //Changes location of cannonBox to a new location to the left
await Task.Delay(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Right)
{
cannonBox.Location = new Point(cannonBox.Left + 2, cannonBox.Top); //Changes location of cannonBox to a new location to the right
await Task.Delay(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Up)
{
createLaser(); //Calls the method whenever Up arrow key is pressed
}
}
}
这样,控制流就会返回给调用者,你的UI线程有时间处理其他事件(因此不需要Application.DoEvents()
)。然后在(大约)10ms之后,返回控件并继续执行该处理程序。
可能需要进行更多的微调,因为当然,当方法尚未完成时,您可以设法击中更多键。如何处理这取决于周围环境。您可以声明一个标志,指示当前执行并拒绝其他方法条目(此处不需要线程安全,因为它在UI线程上按顺序发生)。
或者不是拒绝重新进入队列,而是在另一个事件中按键并处理它们,例如“闲置”事件(如评论中建议的Lasse)。
请注意,事件处理程序是少数情况下使用async
而不返回Task
的情况之一。
答案 1 :(得分:2)
使用计时器,每20毫秒调用一次游戏处理。
在KeyDown
/ KeyUp
个事件中,只需更改游戏处理使用的当前状态。
示例代码:
[Flags]
public enum ActionState
{
MoveLeft,
MeveRight,
FireLaser,
}
// stores the current state
private ActionState _actionState;
// set action state
private void Game_Screen_KeyDown(object sender, KeyEventArgs e)
{
switch ( e.KeyCode )
{
case Keys.Left:
_actionState |= ActionState.MoveLeft;
break;
case Keys.Right:
_actionState |= ActionState.MoveRight;
break;
case Keys.Up:
_actionState |= ActionState.FireLaser;
break;
default:
break;
}
}
// remove action state
private void Game_Screen_KeyUp(object sender, KeyEventArgs e)
{
switch ( e.KeyCode )
{
case Keys.Left:
_actionState &= ~ActionState.MoveLeft;
break;
case Keys.Right:
_actionState &= ~ActionState.MoveRight;
break;
case Keys.Up:
_actionState &= ~ActionState.FireLaser;
break;
default:
break;
}
}
// called from a timer every 20 milliseconds
private void Game_Screen_LoopTimer_Tick(object sender, EventArgs e)
{
if ( _actionState.HasFlag( ActionState.MoveLeft ) && !_actionState.HasFlag( ActionState.MoveRight ) )
{
cannonBox.Location = new Point(cannonBox.Left - 2, cannonBox.Top); //Changes location of cannonBox to a new location to the left
}
if ( _actionState.HasFlag( ActionState.MoveRight ) && !_actionState.HasFlag( ActionState.MoveLeft ) )
{
cannonBox.Location = new Point(cannonBox.Left + 2, cannonBox.Top); //Changes location of cannonBox to a new location to the right
}
if ( _actionState.HasFlag( ActionState.FireLaser ) )
{
createLaser(); //Calls the method whenever Up arrow key is pressed
}
}
答案 2 :(得分:0)
Application.DoEvents()会中断您的方法的执行,UI线程将处理其事件(包括重绘UI)。根据我的经验,在正确的地方使用它并没有错...
使用'async'模式(如RenéVogt建议的那样)是制作reponsive UI的最佳做法。
然而。你必须问自己,如果关键是向左,向右还是向上,你需要一个检查500次的循环。特别是因为看起来这个循环是由一个按键事件触发的......
如果你在main中创建一个'while(true)'循环并从那里调用Application.DoEvents可能会更容易。
或者您对key_down事件做出反应并一次执行操作。 =&GT;按左 - >向左移动 - &gt;再次按左键 - >向左移动一个......等等。
https://msdn.microsoft.com/en-us//library/system.windows.forms.application.doevents(v=vs.110).aspx