• 05/01/13 05:30 PM
    Sign in to follow this  

    Retro Game Programming: How to Make Pong

    General and Gameplay Programming

    Pong is one of the earliest video games to gain popularity. It is a good project to start out with as it has the most basic gameplay. However, it introduces some fundamental concepts which are common to all games.

    Who is this article for

    You should be familiar with the C++ programming language. You should also know how to integrate third party code. You should also have a basic understanding of a rendering engeine and how the game loop works. This article will cover the gameplay, It will NOT cover the concepts that can be abstracted away by Third party tools/engines.

    Requirements to compile the code

    • C++ compiler (I am using Visual Studio 10, but can use an IDE of your choice)
    • Directx X SDK (June 2010)

    Objectives

    • Allow player to move the paddle
    • Check for ball collisions with the paddles and walls
    • Basic AI
    While this project utilizes my custom engine(built on C++ and DirectX), it is possible to extend the logic to other platforms. This project uses 3D models, but you can easily use sprites instead (as long as the engine you are using supports it)

    Making the Game

    Game Elements

    Pong has the following game elements
    • Ball,
    • Left Paddle
    • Right Paddle
    • Top Wall,
    • Bottom Wall,
    All the above elements will be represented with the following
    • Initial Position
    • Current Position
    • Model associated with this game element
    We will create an array of GameElements that will store the data for each of our game elements Game.h class cGame { enum PONGGAMEELEMENTS { PGE_UNKNOWN = -1, PGE_BALL, PGE_PADDLE_LEFT, PGE_PADDLE_RIGHT, PGE_WALL_UP, PGE_WALL_DOWN, PGE_TOTAL } public: // functions and extraneous variables omitted private: cPongGameElement ** m_ppGameElements; // ptr to the gameelements };
    In this project (0,0) lies at the center of the screen. I will be using a cGameElementDef structure which will be populated to create the game elements. This will be passed into the Initialize function of the game element and will setup the model and the initial position
    void VOnInitialization() { //For the paddles cGameElementDef paddleDef; paddleDef.strModelName= "cube"; paddleDef.vPosition= cVector3(m_vScreenTopLeftPos.x, 0.0f, 0.0f); m_ppGameElements[PGE_PADDLE_LEFT] = DEBUG_NEW cPaddle(); m_ppGameElements[PGE_PADDLE_LEFT]->VInitialize(paddleDef); paddleDef.vPosition= cVector3(m_vScreenBottomRightPos.x, 0.0f, 0.0f); m_ppGameElements[PGE_PADDLE_RIGHT] = DEBUG_NEW cPaddle(); m_ppGameElements[PGE_PADDLE_RIGHT]->VInitialize(paddleDef); // for the walls cGameElementDef wallDef; wallDef.strModelName = "cube"; wallDef.vPosition= cVector3(0, m_vScreenTopLeftPos.y, 0.0f); m_ppGameElements[PGE_WALL_UP] = DEBUG_NEW cWall(); m_ppGameElements[PGE_WALL_UP]->VInitialize(wallDef); wallDef.vPosition= cVector3(0, m_vScreenBottomRightPos.y, 0.0f); m_ppGameElements[PGE_WALL_DOWN] = DEBUG_NEW cWall(); m_ppGameElements[PGE_WALL_DOWN]->VInitialize(wallDef); cGameElementDef ballDef; ballDef.strModelName = "sphere"; ballDef.vScale = cVector3(0.5f, 0.5f, 0.5f); pGame->m_ppGameElements[pGame->PGE_BALL] = DEBUG_NEW cBall(); pGame->m_ppGameElements[pGame->PGE_BALL]->VInitialize(ballDef); } void VOnUpdate() { for(int i=0; i) { m_ppGameElements->OnUpdate(m_pGameTimer->VGetDeltaTime()); } } } void Render() { for (int i=0; iPGE_TOTAL; i++) { if(m_ppGameElements && m_ppGameElements) { // Render the game elements model at the current position m_ppGameElements->Render(); } } } void Cleanup() { if(m_ppGameElements) { for (int i=0; i); } SafeDeleteArray(&m_ppGameElements); } } At this point you should see something similar to the image below Step1.png

    Handling Keyboard Input

    For taking player input through the keyboard we will be using the WindowProc callback function. In the code below we use a class function VOnMsgProc to handle the window messages. All we need to do here is check if the appropriate key is pressed and perform the required action. We move the left paddle up/down when W/S is pressed. bool VOnMsgProc(const AppMsg & msg ) { if(!cHumanView::VOnMsgProc(msg)) { if(msg.m_uMsg == WM_KEYDOWN) { if (msg.m_wParam == VK_ESCAPE && !IsKeyLocked(VK_ESCAPE)) { // lock the ESC key LockKey(VK_ESCAPE); PostQuitMessage(-1); } if (msg.m_wParam == 'S') { m_pGame->MoveLeftPaddle(true); } if (msg.m_wParam == 'W') { m_pGame->MoveLeftPaddle(false); } } else if (msg.m_uMsg == WM_KEYUP) { if (msg.m_wParam == VK_ESCAPE) { UnlockKey(VK_ESCAPE); } } } return true; } Game.cpp void MoveLeftPaddle(bool bMoveDown) { cPaddle * pPaddle = m_ppGameElements[PGE_PADDLE_LEFT]->CastToPaddle(); if(pPaddle) { if (bMoveDown) { pPaddle->MoveDown(m_pGameTimer->VGetDeltaTime()); } else { pPaddle->MoveUp(m_pGameTimer->VGetDeltaTime()); } } } Paddle.cpp void MoveDown(const float fElapsedTime) { float fDeltaMovement = m_fMoveFactor * fElapsedTime; cVector3 vPredictedPos = GetPosition(); vPredictedPos.y -= fDeltaMovement; SetPosition(vPredictedPos); } void MoveUp(const float fElapsedTime) { float fDeltaMovement = m_fMoveFactor * fElapsedTime; cVector3 vPredictedPos = GetPosition(); vPredictedPos.y += fDeltaMovement; SetPosition(vPredictedPos); } At this point, when you press 'W' the left paddle should move up and when you press 'S' the paddle should move down. But you will notice that if you keep the key pressed, the paddle goes off-screen. This is because we have no collision checks. The next part will constrain the paddle to be on the screen at all times.

    Keeping the Paddle on Screen

    For all the game elements add a collider to it. For keeping the paddle on the screen we will check if a collision occurs when moving it. If there is a collision, we don't update the paddle's position. Paddle.cpp void MoveDown(const float fElapsedTime) { cContact contact; float fDeltaMovement = m_fMoveFactor * fElapsedTime; shared_ptr const pAABB = IAABB::DuplicateAABB(GetAABB()); pAABB->VTransalate(cVector3(0, -fDeltaMovement, 0)); if (!(ICollisionChecker::GetInstance()->VCheckForCollisions(pAABB.get(), m_pGame->VGetGameElements()[m_pGame->PGE_WALL_DOWN]->GetAABB(), contact))) { cVector3 vPredictedPos = GetPosition(); vPredictedPos.y -= fDeltaMovement; SetPosition(vPredictedPos); } } void MoveUp(const float fElapsedTime) { cContact contact; float fDeltaMovement = m_fMoveFactor * fElapsedTime; shared_ptr const pAABB = IAABB::DuplicateAABB(GetAABB()); pAABB->VTransalate(cVector3(0, fDeltaMovement, 0)); if (!(ICollisionChecker::GetInstance()->VCheckForCollisions(pAABB.get(), m_pGame->VGetGameElements()[m_pGame->PGE_WALL_UP]->GetAABB(), contact))) { cVector3 vPredictedPos = GetPosition(); vPredictedPos.y += fDeltaMovement; SetPosition(vPredictedPos); } }

    Moving the Ball

    To move the ball, we will give it an initial velocity. On each update cycle we will check if it has collided with the wall or paddles and if so then change its direction and/or speed. If the ball goes off screen, we will restart the level. On restarting the level, we give the ball a random velocity. Ball.cpp void VInitialize(const cGameElementDef & def) { cPongGameElement::VInitialize(def); m_pRandomGenerator = IRandomGenerator::CreateRandomGenerator(); m_vSpeed = cVector3(static_cast(m_pRandomGenerator->Random(5,10)), static_cast(m_pRandomGenerator->Random(5,10)), 0.0f); } void OnRestart() { cPongGameElement::OnRestart(); m_vSpeed = cVector3(static_cast(m_pRandomGenerator->Random(5,10)), static_cast(m_pRandomGenerator->Random(5,10)), 0.0f); } void OnUpdate(float fElapsedTime) { cContact contact; cVector3 vDeltaPos = m_vSpeed * fElapsedTime; shared_ptr const pAABB = IAABB::DuplicateAABB(GetAABB()); pAABB->VTransalate(vDeltaPos); cVector3 vPredictedPos = GetPosition(); //check for collision with walls if ((ICollisionChecker::GetInstance()->VCheckForCollisions(pAABB.get(), m_pGame->VGetGameElements()[m_pGame->PGE_WALL_DOWN]->GetAABB(), contact)) || (ICollisionChecker::GetInstance()->VCheckForCollisions(pAABB.get(), m_pGame->VGetGameElements()[m_pGame->PGE_WALL_UP]->GetAABB(), contact))) { float nv = m_vSpeed.Dot(contact.vNormal); m_vSpeed -= contact.vNormal * 2 * nv; } //check for collision with paddles if ((ICollisionChecker::GetInstance()->VCheckForCollisions(pAABB.get(), m_pGame->VGetGameElements()[m_pGame->PGE_PADDLE_LEFT]->GetAABB(), contact)) || (ICollisionChecker::GetInstance()->VCheckForCollisions(pAABB.get(), m_pGame->VGetGameElements()[m_pGame->PGE_PADDLE_RIGHT]->GetAABB(), contact))) { float nv = m_vSpeed.Dot(contact.vNormal); m_vSpeed -= contact.vNormal * 2 * nv; } vPredictedPos = vPredictedPos + vDeltaPos + contact.vDistance; SetPosition(vPredictedPos); // check if ball is off screen if (GetPosition().x VGetScreenTopLeftPos().x) { cGame * pGame = const_cast(m_pGame); pGame->VRoundOver(true); } else if (GetPosition().x > m_pGame->VGetScreenBottomRightPos().x) { cGame * pGame = const_cast(m_pGame); pGame->VRoundOver(false); } cPongGameElement::OnUpdate(fElapsedTime); } Game.cpp void VRoundOver(const bool bPlayer1Won) { if (bPlayer1Won) { // Increment player1 score } else { // Increment player2 score } Restart(); }

    Paddle AI

    If you are running the game, you will realize it's no fun. There is no one competing with you. The AI does not move the paddle to beat you. So let's go ahead and add a basic AI to play against. Our AI will wait for the ball to come in its half of the screen and then move in the (vertical) direction of the ball Game.cpp void VOnUpdate() { //extra code ommitted HandlePaddleAI(m_pGameTimer->VGetDeltaTime()); } void HandlePaddleAI(const float fElapsedTime) { cVector3 vBallPos = m_ppGameElements[PGE_BALL]->GetPosition(); // if the ball is in the players half, there is no need to do anything if (vBallPos.x < 0) { return; } cVector3 vPaddlePos = m_ppGameElements[PGE_PADDLE_RIGHT]->GetPosition(); if (vPaddlePos.y - vBallPos.y < 1) { cPaddle * pPaddle = m_ppGameElements[PGE_PADDLE_RIGHT]->CastToPaddle(); if(pPaddle) { pPaddle->MoveUp(fElapsedTime); } return; } else if (vBallPos.y - vPaddlePos.y < 1) { cPaddle * pPaddle = m_ppGameElements[PGE_PADDLE_RIGHT]->CastToPaddle(); if(pPaddle) { pPaddle->MoveDown(fElapsedTime); } return; } }

    Running the attached executable

    To run the code, You should have Visual Studio 2010 redistributable and DirectX redistributable installed on your machine

    Conclusion

    This concludes "Retro Games: How to Make Pong". The attached game sample has the basic gameplay. I hope you enjoyed this tutorial and it helped you.

    Article Update Log

    1 Apr 2013: Initial release 2 Apr 2013: Updated Formatting. Fixed Grammatical Errors 1 May 2013: Added note to highlight scope of the article 29 May 2013: Added "Who is this article for", "Requirements for compiling the code" and "Running the attached executable" heading


      Report Article
    Sign in to follow this  


    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now


      

    Share this review


    Link to review

      

    Share this review


    Link to review

      

    Share this review


    Link to review

      

    Share this review


    Link to review

      

    Share this review


    Link to review

      

    Share this review


    Link to review

      

    Share this review


    Link to review

      

    Share this review


    Link to review

      

    Share this review


    Link to review

      

    Share this review


    Link to review