Click here to Skip to main content
15,910,118 members
Articles / Desktop Programming / WPF

WPF based exciting Pong Game

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
31 May 2024MIT4 min read 15.3K   18   10
This is a multi player Pong game written in C#/ WPF using Visual Studio 2022
Pongs is a 2 player game in which 2 players move their paddles simultaneously to hit a ball back and forth. Player 1 uses keys 'W', 'A', 'S', and 'D' for up, down, left and right respectively and Player 2 uses arrow keys. Both players attempt to hit the ball to opponents side. If the ball goes past a player's side of the screen, their opponent gains a point.

Quick Demo: click here

Source Code: click here

Description

Pongs is a game where you can move your paddle using the keys W for up movement, A, S, D (likewise) and arrow keys to hit the ball to the other side.

Image 1Image 2
Figure 1 - Gif of game being played

If the ball goes past your side of the screen, the other person gets a point. The game goes on forever and keeps track of your score.

Image 3
Figure 2 - Directions page from game

Settings

In this game, there are also customizable settings such as…

Image 4
Figure 3 - Settings page from game

Ball Speed - Changes the speed at which the ball moves
Ball Size - Changes the size of the ball
Paddle Speed - Changes the speed at which the paddles move
Paddle Size - Changes the size of the paddles
Rounds to Win - The amount of wins a player needs to win the game

There are also pause and restart buttons which either pause the game until clicked again or restart the game respectively.

Image 5
Figure 4 - Pause and restart button from game

Background

This game was made using WPF and C# on Visual Studio in the span of one week. It was made to obtain the basics of WPF and how to use C# code behind to create dynamic objects in the code. 

Code Explanation

Drawing the board

One key part of my code is drawing everything on the board, such as the paddle and the ball. This is done through 3 different functions.

public void DrawPaddle(Rectangle Paddle, double x, double y)
        {
            log.Info("DrawPaddle Start");
            Paddle.Width = 1.5 * SliderInfo.PaddleSize;
            Paddle.Height = 7.8 * SliderInfo.PaddleSize;
            Paddle.Fill = Brushes.Black;
            Paddle.Stroke = Brushes.White;
            Paddle.StrokeThickness = 4;

            Canvas.SetTop(Paddle, y);
            Canvas.SetLeft(Paddle, x);

            log.Info("DrawPaddle End");
        }
The above code draws the paddle onto the board. It is drawn separate from the other shapes since it is the only shape that moves via player input, and also requires inputs, such as which paddle and the x and y coordinates of it.
public void ReDraw()
{
    log.Info("ReDraw Start");
    if (WindowState == WindowState.Maximized)
    {
        Window.Height = (int)System.Windows.SystemParameters.PrimaryScreenHeight;
        Window.Width = (int)System.Windows.SystemParameters.PrimaryScreenWidth;
    }

    Board.Width = Window.Width;
    Board.Height = Window.Height;

    sbkGameEngine.y1 = Board.Height / 2 - (paddle1.Height / 2);
    sbkGameEngine.y2 = sbkGameEngine.y1;
    sbkGameEngine.x2 = Board.Width - 32;
    sbkGameEngine.x1 = 0;

    Ball.Width = 1.5 * SliderInfo.BallSize;
    Ball.Height = 1.5 * SliderInfo.BallSize;
    Ball.Fill = Brushes.White;
    Ball.Stroke = Brushes.White;
    Ball.StrokeThickness = 4;
    Canvas.SetTop(Ball, Board.Height / 2 - Ball.Height / 2);
    Canvas.SetLeft(Ball, Board.Width / 2 - Ball.Width / 2);

    Menu.Width = Board.Width;
    if (Board.Width - (SettingsMenu.Width + About.Width + Help.Width) - (1.5 * RestartButton.Width + PauseButton.Width) - 2 > 0)
    {
        Spacer.Width = Board.Width - (SettingsMenu.Width + About.Width + Help.Width) - (1.5 * RestartButton.Width + PauseButton.Width) - 2;
    }

    P1Scoreboard.Text = "" + sbkGameEngine.P1Score;
    P2Scoreboard.Text = "" + sbkGameEngine.P2Score;

    sbkGameEngine.CanBallMove = false;
    ReDrawUnmoving();
    log.Info("ReDraw End");
}

This function draws all of the unmoving shapes, as well as the menu and board. This is done to initially set up the board and to reset the board when necessary.

private void ReDrawUnmoving()
{
    log.Info("ReDrawUnmoving Start");
    Boundary.Stroke = Brushes.Gray;
    Boundary.StrokeThickness = 3;
    Boundary.X1 = Board.Width / 2;
    Boundary.X2 = Board.Width / 2;
    Boundary.Y1 = 0;
    Boundary.Y2 = Board.Height;

    BottomWall.Width = Board.Width;
    BottomWall.Height = 24;
    BottomWall.Fill = Brushes.White;
    BottomWall.Stroke = Brushes.White;
    BottomWall.StrokeThickness = 4;
    Canvas.SetTop(BottomWall, Board.Height - 63);
    Canvas.SetLeft(BottomWall, 0);

    log.Info("ReDrawUnmoving End");
}

This function does the same thing as ReDraw(), just this time drawing the unmoving parts.

These functions allow for the board to be drawn, which a is key element of my game. This is because knowing where everything is on the board is necessary for the player to react in the right way.

Moving the Ball

Another key part of my game is the movement of the ball. This is because the main point of the game is to try and block the ball from moving into your side of the field.

public void BallMovement()
        {
            log.Info("BallMovement Start");
            if (sbkGameEngine.CanBallMove && sbkGameEngine.GamePlayable)
            {
                Canvas.SetTop(Ball, Canvas.GetTop(Ball) + sbkGameEngine.VMovement);
                Canvas.SetLeft(Ball, Canvas.GetLeft(Ball) + sbkGameEngine.HMovement);
            }
            if (sbkGameEngine.P1Wins)
            {
                WhoWon_.Text = "Player 1 Wins!";
                WhoWon_.Visibility = Visibility.Visible;
                RestartText.Visibility = Visibility.Visible;
                OnPause(Ball, a);
                sbkGameEngine.i = 2;
            }
            if (sbkGameEngine.P2Wins)
            {
                WhoWon_.Text = "Player 2 Wins!";
                WhoWon_.Visibility = Visibility.Visible;
                RestartText.Visibility = Visibility.Visible;
                OnPause(Ball, a);
                sbkGameEngine.i = 2;
            }
            log.Info("BallMovement End");
        }

This function only takes the variables from the sbkGameEngine class to move the ball. It doesn't do any calculations itself and just does what the engine tells it to do via the variables that are changed by the sbkGameEngine.

public void BallMovement()
        {
            log.Info("BallMovement Start");
            if (GamePlayable)
            {
                int P1Top = (int)Canvas.GetTop(mGuiReference.paddle1);
                int P1Bottom = (int)(Canvas.GetTop(mGuiReference.paddle1) + mGuiReference.paddle1.Height);
                int P1Left = (int)Canvas.GetLeft(mGuiReference.paddle1);
                int P1Right = (int)(Canvas.GetLeft(mGuiReference.paddle1) + mGuiReference.paddle1.Width);
                int P2Top = (int)Canvas.GetTop(mGuiReference.paddle2);
                int P2Bottom = (int)(Canvas.GetTop(mGuiReference.paddle2) + mGuiReference.paddle2.Height);
                int P2Left = (int)Canvas.GetLeft(mGuiReference.paddle2);
                int P2Right = (int)(Canvas.GetLeft(mGuiReference.paddle2) + mGuiReference.paddle2.Width);
                int BallTop = (int)Canvas.GetTop(mGuiReference.Ball);
                int BallBottom = (int)(Canvas.GetTop(mGuiReference.Ball) + mGuiReference.Ball.Height);
                int BallLeft = (int)Canvas.GetLeft(mGuiReference.Ball);
                int BallRight = (int)(Canvas.GetLeft(mGuiReference.Ball) + mGuiReference.Ball.Width);

                if ((P2Bottom > BallTop && P2Top < BallBottom && BallLeft < P2Right && BallRight > P2Left && HMovement == 1) || (P1Bottom > BallTop && P1Top < BallBottom && BallLeft < P1Right && BallRight > P1Left && HMovement == -1))
                {
                    HMovement *= -1;
                    Console.Beep(37, 10);
                }
                if (BoundaryCheck(mGuiReference.Ball, 25, (int)(mGuiReference.Board.Height - (mGuiReference.BottomWall.Height * 2) - 5), 0, 0, true, true, false, false) == false)
                {
                    VMovement *= -1;
                    Console.Beep(70, 5);
                }
                if (Canvas.GetLeft(mGuiReference.Ball) >= mGuiReference.Board.Width)
                {
                    P1Score++;
                    mGuiReference.ReDraw();
                    log.Info("Player 1 scored!");
                    if (P1Score == SliderInfo.RoundsToWin)
                    {
                        P1Wins = true;
                    }
                }
                if (Canvas.GetLeft(mGuiReference.Ball) + 15 <= 0)
                {
                    P2Score++;
                    mGuiReference.ReDraw();
                    log.Info("Player 2 scored!");
                    if (P2Score == SliderInfo.RoundsToWin)
                    {
                        P1Wins = true;
                    }
                }
            }
            log.Info("BallMovement End");
        }

This function is the one actually doing the calculations. It changes the variables controlling which direction the ball is moving when certain conditions are met, like swapping directions after hitting a paddle.

Both these functions combine to move the ball around and allow for the ball to interact with it's environment.

Paddle Movement

Another element necessary to the creation of the game is the movement of the paddles, since it is the only player controlled shape.

public void OnKeyDown(object sender, KeyEventArgs e)
        {
            log.Info("OnKeyDown Start");
            if (AllowedKeys.Contains(e.Key))
            {
                if (i == 0)
                {
                    KeysPressed.Add(e.Key);
                    CanBallMove = true;
                }
            }
            log.Info("OnKeyDown End");
        }
This function, when a key is pressed, adds that key to a hashset. The hashset will be later used to identify which keys are being pressed and which are not.
public void OnKeyUp(object sender, KeyEventArgs e)
        {
            log.Info("OnKeyUp Start");
            if (KeysPressed.Contains(e.Key))
            {
                KeysPressed.Remove(e.Key);
            }
            log.Info("OnKeyUp End");
        }
This function removes the let go key and runs when a key is pressed.
public void PressedKeys(/*object? sender, EventArgs e*/)
        {
            log.Info("PressedKeys Start");
            if (GamePlayable)
            {
                if (KeysPressed.Contains(Key.Up) && BoundaryCheck(mGuiReference.paddle2, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width, true, false, false, false) == true)
                {
                    y2 -= 2;
                }
                if (KeysPressed.Contains(Key.W) && BoundaryCheck(mGuiReference.paddle1, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width, true, false, false, false) == true)
                {
                    y1 -= 2;
                }

                if (KeysPressed.Contains(Key.Down) && BoundaryCheck(mGuiReference.paddle2, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width, false, true, false, false) == true)
                {
                    y2 += 2;
                }
                if (KeysPressed.Contains(Key.S) && BoundaryCheck(mGuiReference.paddle1, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width, false, true, false, false) == true)
                {
                    y1 += 2;
                }

                if (KeysPressed.Contains(Key.Left) && BoundaryCheck(mGuiReference.paddle2, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width / 2, false, false, true, false) == true)
                {
                    x2 -= 2;
                }
                if (KeysPressed.Contains(Key.A) && BoundaryCheck(mGuiReference.paddle1, 25, (int)mGuiReference.Board.Height - 78, 0, (int)mGuiReference.Board.Width / 2, false, false, true, false) == true)
                {
                    x1 -= 2;
                }

                if (KeysPressed.Contains(Key.Right) && BoundaryCheck(mGuiReference.paddle2, 25, (int)mGuiReference.Board.Height - 78, 0, (int)mGuiReference.Board.Width - 20, false, false, false, true) == true)
                {
                    x2 += 2;
                }
                if (KeysPressed.Contains(Key.D) && BoundaryCheck(mGuiReference.paddle1, 25, (int)mGuiReference.Board.Height - 78, 0, (int)mGuiReference.Board.Width / 2, false, false, false, true) == true)
                {
                    x1 += 2;
                }
            }
            log.Info("PressedKeys End");
        }        

The above function is run repeatedly by a different function and checks if any keys are in the hashset. If a key is in the hashset, it will do the corresponding move, such as moving the player 2 paddle up when the up key is in the hashset.

These all combine to allow player key inputs to be able to correspond to actions taken by the paddles in the game. 

Running the Application

  1. Go to the GitHub link: click here
  2. Go to the folder labeled "Quick Demo"
  3. Download the "ZippedDemo.zip"
  4. Unzip the file
  5. Run Pongs.exe

History

V1.0 -- May 5th 2024 - First version

v1.1 -- May 22nd 2024 - Revised abstract

v1.2 -- May 23rd 2024 - Updated steps to run the application

v1.3 -- May 24th 2024 - Updated code

v1.4 -- May 27 2024 - Added settings header

v1.5 -- May 29 2024 - Added figure numbers and descriptions

v1.6 -- May 31 2024 - Fixed gif that was previously not loading

References

1. https://stackoverflow.com/

2. https://learn.microsoft.com/en-us/dotnet/

 

If you found this article helpful / interesting, please don't forget to Vote! 

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionComputer Player Pin
Hyland Computer Systems24-May-24 4:09
Hyland Computer Systems24-May-24 4:09 
AnswerRe: Computer Player Pin
Sbk12324-May-24 9:35
Sbk12324-May-24 9:35 
GeneralMy vote of 5 Pin
theDiscountCodes24-May-24 1:42
professionaltheDiscountCodes24-May-24 1:42 
GeneralRe: My vote of 5 Pin
Sbk12324-May-24 9:24
Sbk12324-May-24 9:24 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA22-May-24 21:12
professionalȘtefan-Mihai MOGA22-May-24 21:12 
GeneralRe: My vote of 5 Pin
Sbk12323-May-24 9:42
Sbk12323-May-24 9:42 
QuestionMy vote of 5 Pin
Peter Huber SG19-May-24 23:58
mvaPeter Huber SG19-May-24 23:58 
AnswerRe: My vote of 5 Pin
Sbk12320-May-24 12:35
Sbk12320-May-24 12:35 
GeneralRe: My vote of 5 Pin
Peter Huber SG20-May-24 15:45
mvaPeter Huber SG20-May-24 15:45 
GeneralRe: My vote of 5 Pin
Sbk12322-May-24 10:59
Sbk12322-May-24 10:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.