Introduction
I'll present you the way in which I wrote a small Pong game in C# WinForm.
Using the Code
Let's declare global variables first. We have const
integer values called limit_Pad
presenting the bottom limit of the paddle, limit_Ball
- the limit of the ball before bouncing up. Then, we have regular integer values computer_won
and player_won
that count victories of players, speed_Top
and speed_Left
setting a direction and movement of the ball, and boolean values up and down to decide where the paddles will go, and a game variable that prevents player from moving the paddle before the game is started. On the bottom is one Random()
class held in r
instance:
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();
The first thing to do was to create a pattern of moving paddles. For that purpose, I needed 2 events - KeyDown
which detects whether the button W is pressed for moving the paddle up or S button for moving the paddle down by activating a timer
which moves the paddle up or down by 3 units. The KeyUp
event detects when the button is released and stops the timer
controlling the paddle:
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;
}
}
The PC paddle movement is simple. It's run by a timer
called Computer()
, and it contains two parts; the first one which limits the paddle movement by the top and bottom, and the second that follows the ball movement regardless of whether the ball is below half paddle height or above:
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;
}
}
Now let's move for the ball movement part. First thing to do is to set the ball direction values. Upon start, the ball moves towards Player
paddle by the Left
property coordinate only. For that purpose, we create two procedures: one sets the values, and the other one makes the ball move:
private void StartValues()
{
speed_Top = 0;
speed_Left = -5;
}
private void BallMoves()
{
Ball.Top += speed_Top;
Ball.Left += speed_Left;
}
The next thing to do is to limit the ball borders by vertical and horizontal line. Vertically, when the ball hits the ceiling, it bounces back in the opposite direction. So what we have to do is multiply the speed_Top
variable by -1
and it will change its course regardless of hitting the upper or lower bound:
private void HitBorder()
{
if (Ball.Location.Y <= 0 || Ball.Location.Y >= limit_Ball)
{
speed_Top *= -1;
}
}
Horizontally, there's a bit more work to do. The main procedure called BallLeftField()
will contain multiple smaller procedures. PlayerWon()
and ComputerWon()
will increment their global int
variables and write them to the Label
controls:
private void PlayerWon()
{
player_won++;
label1.Text = player_won.ToString();
}
private void ComputerWon()
{
computer_won++;
label3.Text = computer_won.ToString();
}
Side that wins 10 rounds first is the winner, so when the game is over, we have to set the variables and controls at their starting positions. For that purpose, I used EndGame()
procedure. It also sets the bool
value game
to false
preventing the players paddle to move while the game is inactive, as well as setting the computer_won
and player_won
values to 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;
}
And when the ball left field by horizontal line, we have to set its position back to center and which side took a round, the opposite side gets a serve:
private void NewPoint(int n)
{
const int x = 227,y = 120;
Ball.Location = new Point(x, y);
speed_Top = 0;
speed_Left = n;
}
The BallLeftField()
procedure:
private void BallLeftField()
{
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();
}
}
The most important part of the ball movement was to determine where the ball collided with paddle. In order to do that, I split paddle in 5 parts from up to down:
Upper
- Ball collision position is greater or equal than pad top minus ball height and lower or equal than pad top plus ball height High
- Ball colliding position is greater than pad top + ball height and lesser or equal than pad top + 2 times ball height Middle
- Ball colliding position is greater than pad top + 2 times ball height and lesser or equal than pad top + 3 times pad top Low
- Ball position is greater than pad top + 3 times ball height and lesser or equal than 4 times pad top + ball height Bot
- Ball position greater than pad top + 4 times ball height and lesser and equal than pad bottom plus ball height
For that purpose, I created 5 bool
functions:
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;
}
What we must know is which exact coordinates to give the ball depending on which paddle - Players or PCs it collided with. First, I'll write a function that returns negative value of randomly given number:
private int Negative(int i,int n)
{
int myval = r.Next(i, n) * -1;
return myval;
}
Then depending on the ball position (either greater than half of a form width or greater), the paddle will launch the ball towards the opposite side giving its speed_Left
variable positive or negative value:
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;
}
And there is the common Collision()
procedure for both paddles. It takes the bool outcomes of switch
statements which check at what part the paddle ball collided with giving the ball proper coordinates for moving the opposite side:
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();
}
I forgot to mention the Edge()
procedure. As I was testing the game, I noticed that while hitting far sides of the paddles would bounce the ball back in the field even if it looked like the ball would leave the form, it even looked as the horizontal borders bounced the ball back in field which defies the logic of the game. So I wrote a procedure that launches the ball out the bounds if it collided with a small part of a paddle leaning to the forms horizontal edges:
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;
}
}
}
When all the functions and procedures are done, we can move to the main timer
which follows the entire ball movement. First, it checks if the ball collided with a Player setting the ball coordinates and launching it towards the enemy, then doing the same for the PC part. After that, it checks if the ball collided with upper\lower bounds changing its Top
direction, or if the ball left field setting the ball to the center of the form and firing it:
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();
}
And the final part, the "Start Game" button. Upon clicking, it sets the starting ball direction values, then sets bool
value game
to true
enabling the Player
to move the paddle, enabling all 3 timers making the ball move as well as the Enemy(PC).
private void button1_Click(object sender, EventArgs e)
{
StartValues();
game = true;
button1.Visible = false;
timer1.Start();
timer2.Start();
timer3.Start();
}
And that's it! It's nothing special, but I hope it might give some idea to beginners.
History
- 4th November, 2019: Initial version