65.9K
CodeProject 正在变化。 阅读更多。
Home

小型 WinForm 乒乓球游戏 - C#

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.86/5 (7投票s)

2019年11月4日

CPOL

5分钟阅读

viewsIcon

27732

downloadIcon

1208

用 C# 编写的简单 WinForm 乒乓球游戏

引言

我将向您展示我如何用C# WinForm编写一个小型的乒乓球游戏。

Using the Code

首先,我们声明全局变量。我们有const整数值,称为limit_Pad,表示挡板的底部限制;limit_Ball,表示球在向上反弹之前的限制。然后,我们有常规整数值computer_wonplayer_won,用于计算玩家的胜利次数;speed_Topspeed_Left,用于设置球的方向和移动;以及布尔值updown,用于决定挡板的移动方向;还有一个游戏变量,用于防止玩家在游戏开始前移动挡板。底部有一个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_wonplayer_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日:初始版本
© . All rights reserved.