小型 WinForm 乒乓球游戏 - C#






2.86/5 (7投票s)
用 C# 编写的简单 WinForm 乒乓球游戏
引言
我将向您展示我如何用C# WinForm编写一个小型的乒乓球游戏。
Using the Code
首先,我们声明全局变量。我们有const
整数值,称为limit_Pad
,表示挡板的底部限制;limit_Ball
,表示球在向上反弹之前的限制。然后,我们有常规整数值computer_won
和player_won
,用于计算玩家的胜利次数;speed_Top
和speed_Left
,用于设置球的方向和移动;以及布尔值up
和down
,用于决定挡板的移动方向;还有一个游戏变量,用于防止玩家在游戏开始前移动挡板。底部有一个Random()
类,保存在r
实例中。
const int limit_Pad = 170;
const int limit_Ball = 245;
int computer_won = 0;
int player_won = 0;
int speed_Top;
int speed_Left;
bool up = false;
bool down = false;
bool game = false;
Random r = new Random();
首先要做的是创建挡板的移动模式。为此,我需要两个事件——KeyDown
,它检测是否按下了W键来向上移动挡板,或者按下S键来向下移动挡板,通过激活一个timer
来使挡板向上或向下移动3个单位。KeyUp
事件检测按钮释放时,并停止控制挡板的timer
。
private void Pressed(object sender, KeyEventArgs e)
{
if (game)
{
if (e.KeyCode == Keys.Up || e.KeyCode == Keys.W)
{
up = true;
}
else if (e.KeyCode == Keys.Down || e.KeyCode == Keys.S)
{
down = true;
}
timer1.Start();
}
}
private void Released(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.W || e.KeyCode == Keys.W)
{
up = false;
}
else if (e.KeyCode == Keys.Down || e.KeyCode == Keys.S)
{
down = false;
}
timer1.Stop();
}
private void MovePaddle(object sender, EventArgs e)
{
if (up && Player.Location.Y > 0)
{
Player.Top -= 3;
}
else if (down && Player.Location.Y < limit_Pad)
{
Player.Top += 3;
}
}
电脑挡板的移动很简单。它由一个名为Computer()
的timer
驱动,它包含两个部分:第一部分限制挡板的顶部和底部移动,第二部分跟踪球的移动,而不管球是在挡板高度的一半以下还是以上。
private void Computer(object sender, EventArgs e)
{
if (PC.Location.Y <= 0)
{
PC.Location = new Point(PC.Location.X, 0);
}
else if (PC.Location.Y >= limit_Pad)
{
PC.Location = new Point(PC.Location.X, limit_Pad);
}
if (Ball.Location.Y < PC.Top + (PC.Height / 2))
{
PC.Top -= 3;
}
else if (Ball.Location.Y > PC.Top + (PC.Height / 2))
{
PC.Top += 3;
}
}
现在让我们来处理球的移动部分。首先要做的是设置球的方向值。开始时,球只沿着Left
属性坐标向Player
挡板移动。为此,我们创建了两个过程:一个用于设置值,另一个用于让球移动。
private void StartValues()
{
speed_Top = 0;
speed_Left = -5;
}
private void BallMoves()
{
Ball.Top += speed_Top;
Ball.Left += speed_Left;
}
接下来要做的是限制球的边界,即垂直和水平线。垂直方向上,当球碰到天花板时,它会朝相反的方向反弹。所以我们要做的就是将speed_Top
变量乘以-1
,它就会改变方向,无论它撞到的是上边界还是下边界。
private void HitBorder()
{
if (Ball.Location.Y <= 0 || Ball.Location.Y >= limit_Ball)
{
speed_Top *= -1;
}
}
水平方向上,工作量稍大一些。名为BallLeftField()
的主过程将包含多个较小的过程。PlayerWon()
和ComputerWon()
将增加它们的全局int
变量,并将它们写入Label
控件。
private void PlayerWon()
{
player_won++;
label1.Text = player_won.ToString();
}
private void ComputerWon()
{
computer_won++;
label3.Text = computer_won.ToString();
}
先赢得10轮的一方获胜,所以当游戏结束时,我们需要将变量和控件重置到起始位置。为此,我使用了EndGame()
过程。它还将布尔值game
设置为false
,防止玩家挡板在游戏不活跃时移动,同时将computer_won
和player_won
值设置为0
。
private void EndGame()
{
Player.Location = new Point(0, 75);
PC.Location = new Point(454, 75);
player_won = 0;
computer_won = 0;
label1.Text = player_won.ToString();
label3.Text = computer_won.ToString();
timer1.Stop();
timer2.Stop();
timer3.Stop();
button1.Visible = true;
game = false;
}
当球沿着水平线离开场地时,我们需要将其位置重置到中心,并由赢得一轮的一方发球。
private void NewPoint(int n)
{
const int x = 227,y = 120;
Ball.Location = new Point(x, y);
speed_Top = 0;
speed_Left = n;
}
BallLeftField()
过程
private void BallLeftField() // Slightly different from the project code:
{
if (player_won == 10 || computer_won == 10)
{
EndGame();
}
if (Ball.Location.X < 0)
{
NewPoint(5);
ComputerWon();
}
else if (Ball.Location.X > this.ClientSize.Width)
{
NewPoint(-5);
PlayerWon();
}
}
球移动最重要的部分是确定球何时与挡板发生碰撞。为了做到这一点,我将挡板从上到下分成了5个部分。
Upper
- 球碰撞位置大于或等于挡板顶部减去球的高度,并且小于或等于挡板顶部加上球的高度。High
- 球碰撞位置大于挡板顶部+球的高度,并且小于或等于挡板顶部+2倍球的高度。Middle
- 球碰撞位置大于挡板顶部+2倍球的高度,并且小于或等于挡板顶部+3倍挡板顶部。Low
- 球位置大于挡板顶部+3倍球的高度,并且小于或等于4倍挡板顶部+球的高度。Bot
- 球位置大于挡板顶部+4倍球的高度,并且小于或等于挡板底部+球的高度。
为此,我创建了5个bool
函数。
private bool Upper(PictureBox Pad)
{
return Ball.Location.Y >= Pad.Top - Ball.Height && Ball.Location.Y <= Pad.Top + Ball.Height;
}
private bool High(PictureBox Pad)
{
return Ball.Location.Y > Pad.Top + Ball.Height && Ball.Location.Y <= Pad.Top + 2 * Ball.Height;
}
private bool Middle(PictureBox Pad)
{
return Ball.Location.Y > Pad.Top + 2 * Ball.Height && Ball.Location.Y <= Pad.Top + 3 * Ball.Height;
}
private bool Low(PictureBox Pad)
{
return Ball.Location.Y > Pad.Top + 3 * Ball.Height && Ball.Location.Y <= Pad.Top + 4 * Ball.Height;
}
private bool Bot(PictureBox Pad)
{
return Ball.Location.Y > Pad.Top + 4 * Ball.Height && Ball.Location.Y <= Pad.Bottom + Ball.Height;
}
我们需要知道的是,根据球与哪个挡板(玩家挡板还是电脑挡板)发生碰撞,应该给予球确切的坐标。首先,我将编写一个返回随机数负值的函数。
private int Negative(int i,int n)
{
int myval = r.Next(i, n) * -1;
return myval;
}
然后,根据球的位置(大于窗体宽度的一半或更大),挡板会将球朝相反的方向发射,给它的speed_Left
变量赋予正值或负值。
private int AdjustCoordinates(int i,int n)
{
int res = 0;
if (Ball.Location.X < this.Width / 2)
{
res = r.Next(i, n);
}
else if (Ball.Location.X > this.Width / 2)
{
res = Negative(i, n);
}
return res;
}
还有为两个挡板共用的Collision()
过程。它接收switch
语句的布尔结果,这些语句检查球与挡板的哪个部分发生碰撞,从而为球提供正确的坐标以向对方发射。
private void Collision(PictureBox Paddle)
{
switch (true)
{
case true when Upper(Paddle):
speed_Top = Negative(4, 6);
speed_Left = AdjustCoordinates(5, 6);
break;
case true when High(Paddle):
speed_Top = Negative(2, 3);
speed_Left = AdjustCoordinates(6, 7);
break;
case true when Middle(Paddle):
speed_Top = 0;
speed_Left = AdjustCoordinates(5, 5);
break;
case true when Low(Paddle):
speed_Top = r.Next(2, 3);
speed_Left = AdjustCoordinates(6, 7);
break;
case true when Bot(Paddle):
speed_Top = r.Next(4, 6);
speed_Left = AdjustCoordinates(5, 6);
break;
}
Edge();
}
我忘了提Edge()
过程。在我测试游戏时,我注意到,当球撞到挡板的远侧时,即使看起来球要离开窗体,它也会被弹回场内,甚至看起来水平边界也会将球弹回场内,这违背了游戏的逻辑。所以,我写了一个过程,当球与倾斜到窗体水平边缘的挡板的一小部分碰撞时,就将球发射出边界。
private void Edge()
{
if (Ball.Location.X < this.Width / 2)
{
if (Ball.Location.X < 0 + Ball.Height / 3)
{
speed_Left *= -1;
}
}
else if (Ball.Location.X > this.Width / 2)
{
if (Ball.Location.X > PC.Location.X + (Ball.Width /3))
{
speed_Left *= -1;
}
}
}
当所有函数和过程都完成后,我们可以转到跟踪整个球运动的主timer
。首先,它检查球是否与玩家挡板碰撞,设置球的坐标并将其朝敌方发射,然后对电脑挡板做同样的事情。之后,它检查球是否与上下边界碰撞,改变其Top
方向,或者如果球离开了场地,就将球重置到窗体中心并发射。
private void MoveBall(object sender, EventArgs e)
{
if (Ball.Bounds.IntersectsWith(Player.Bounds))
{
Collision(Player);
}
else if (Ball.Bounds.IntersectsWith(PC.Bounds))
{
Collision(PC);
}
HitBorder();
BallLeftField();
BallMoves();
}
最后一部分是“开始游戏”按钮。点击后,它设置初始的球方向值,然后将布尔值game
设置为true
,启用Player
挡板的移动,并启用所有3个计时器,使球和电脑挡板开始移动。
private void button1_Click(object sender, EventArgs e)
{
StartValues();
game = true;
button1.Visible = false;
timer1.Start();
timer2.Start();
timer3.Start();
}
就是这样!没什么特别的,但我希望它能给初学者一些启发。
历史
- 2019年11月4日:初始版本