我正在使用Visual C#2005编写一个俄罗斯方块游戏。这是我设计的最广泛的程序。
我创建了一个形状类和一个块类来控制不同俄罗斯方块的位置,移动和显示。我有每个形状的moveDown(),moveLeft()和moveRight()函数(和相应的canMoveDown(),canMoveLeft(),canMoveRight()布尔函数,验证它可以移动)。这一切都很美妙。
我想使用向下,向右和向左箭头键让用户移动块,除了使用计时器让形状每隔几毫秒自动下降一行。
我正在使用KeyDown事件处理程序检查用户何时按下向下,向左和向右箭头键。这不是那么难。问题是我想允许对角线运动,我希望它能够顺利运行。我已经尝试了一系列不同的方法来解决这个问题,并取得了不同程度的成功。但我不能说得对......
我最成功的方法是使用三个布尔变量来跟踪向下,向左和向右箭头键的按下时间。我会在KeyDown事件中将布尔值设置为true,在KeyUp事件中将布尔值设置为false。在KeyDown事件中,我还将告诉块如何移动,使用布尔变量来检查当前正在按下哪个组合。除了一件事,它的效果非常好。
如果我按下其中一个箭头键并按住,然后按下第二个箭头键然后释放第二个键,该块将完全停止移动,而不是继续沿着第一个箭头键的方向移动尚未发布。我认为这是因为第二个键触发了KeyDown事件,并且在释放时,KeyUp事件被触发,并且KeyDown事件完全停止触发,即使第一个键被触发。
我不能为我的生活找到一个满意的解决方案来解决这个问题。
非常感谢任何帮助=)
答案 0 :(得分:9)
大多数游戏都不等待事件。他们在必要时轮询输入设备并采取相应的行动。事实上,如果你看过XNA,你会发现它们是你在更新例程中调用的Keyboard.GetState()方法(或Gamepad.GetState()),并且基于它更新你的游戏逻辑结果。使用Windows.Forms时,没有任何开箱即用的功能,但是你可以P / Invoke GetKeyBoardState()函数来利用它。这样做的好处是,您可以一次轮询多个键,因此您可以一次对多个按键做出反应。这是我在网上找到的一个简单的课程,有助于此:
http://sanity-free.org/17/obtaining_key_state_info_in_dotnet_csharp_getkeystate_implementation.html
为了演示,我写了一个简单的Windows应用程序,基本上根据键盘输入移动球。它使用我链接到的类来轮询键盘的状态。您会注意到,如果您一次按住两个键,它将会对角移动。
首先,Ball.cs:
public class Ball
{
private Brush brush;
public float X { get; set; }
public float Y { get; set; }
public float DX { get; set; }
public float DY { get; set; }
public Color Color { get; set; }
public float Size { get; set; }
public void Draw(Graphics g)
{
if (this.brush == null)
{
this.brush = new SolidBrush(this.Color);
}
g.FillEllipse(this.brush, X, Y, Size, Size);
}
public void MoveRight()
{
this.X += DX;
}
public void MoveLeft()
{
this.X -= this.DX;
}
public void MoveUp()
{
this.Y -= this.DY;
}
public void MoveDown()
{
this.Y += this.DY;
}
}
真的没什么特别的......
然后是Form1代码:
public partial class Form1 : Form
{
private Ball ball;
private Timer timer;
public Form1()
{
InitializeComponent();
this.ball = new Ball
{
X = 10f,
Y = 10f,
DX = 2f,
DY = 2f,
Color = Color.Red,
Size = 10f
};
this.timer = new Timer();
timer.Interval = 20;
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
var left = KeyboardInfo.GetKeyState(Keys.Left);
var right = KeyboardInfo.GetKeyState(Keys.Right);
var up = KeyboardInfo.GetKeyState(Keys.Up);
var down = KeyboardInfo.GetKeyState(Keys.Down);
if (left.IsPressed)
{
ball.MoveLeft();
this.Invalidate();
}
if (right.IsPressed)
{
ball.MoveRight();
this.Invalidate();
}
if (up.IsPressed)
{
ball.MoveUp();
this.Invalidate();
}
if (down.IsPressed)
{
ball.MoveDown();
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (this.ball != null)
{
this.ball.Draw(e.Graphics);
}
}
}
简单的小应用程序。只需创建一个球和一个计时器。每20毫秒,它会检查键盘状态,如果按下某个键,它会移动它并使其无效,以便重新绘制。
答案 1 :(得分:1)
如果您依靠密钥重复来重复发送按键事件以使块移动,我认为这不是您想要的方式。块应始终独立于键重复移动。因此,您不应该在关键事件期间移动块。您应该只在keydown和keyup事件期间跟踪键的状态,并处理其他地方的移动。实际的移动应该发生在某种计时器事件中(即使没有任何事情发生,计时器控制也会定期触发事件),或者你应该有一个主循环不断地检查所有事物的状态并在适当的时候移动物体。如果您使用第二个选项,则需要查看“DoEvents”,因为如果您的代码一直在运行而没有完成该功能,程序将不会处理任何其他事件,如keyup和keydown事件。因此,您需要在每个循环中调用DoEvents来处理键事件(以及移动窗口等)。如果你不需要经常处理事情,你可能还想调用System.Threading.Thread.Sleep。如果你使用计时器控件,你不必担心任何一个。
答案 2 :(得分:0)
对不起,没有具体的帮助...毕竟是星期六晚上......但是:
每个键都需要一个状态。当你得到一个keydown事件时,你可以知道哪个键发生故障,并修改你正在维护的keystate。当钥匙重新启动时,再一次,通过查看事件,你可以知道它是什么。仅修改触发事件的键的状态:即不使用keyup事件重置所有状态,否则您在内存中保存的键的状态将被破坏,如您所见。