Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.



Published on

  • Be the first to comment

  • Be the first to like this


  1. 1. Pong One of the first games. Tennis for Two and Space War were earlier, but they didn't have a score. This is the first game and where possible, simplifications will be made. The goal is to develop game construction principles that can be scaled. Rather than jump to what is the best solution, the code starts off simple and will be improved on. Focus on controls, core mechanic, playfield, design, and programming. Objectives ● developing a plan: features, iterations, refactoring, scaling ● setting up a 3D camera ● drawing the court, paddles, ball in 3D ● setting up a 2D camera and drawing the heads up display in 2D ● handling player input: keyboard and joystick ● collisions with the playfield, reflections ● collisions with paddles, point versus rectangle ● dynamically created objects, pointers, clean coding ● game controller class follows the singleton pattern ● recording and playing sounds Introduction These are the notes that go along with the presentation. There are lots of game development concepts to learn from even the simplest game. And pong is a simple game. This development doesn't attempt to re-create a fun version of pong. Development Plan: 1000 Features The agile practice of creating a list of features you want in your game seems to work best. Write out a list and give each feature an unique rank from 1000 to 0 -- 1000 being the most important to your game. Generally devote more time and energy to the higher ranking features. The feature list should include only things that can be seen, heard, felt, or otherwise tested by playing the game and not include work under the hood. basic court, ball, paddles are drawn • ball collisions with the walls • input from keyboard • input from joysticks • ball collisions with the player paddles •
  2. 2. score displayed • game waits for start button, plays, ends • ball speeds up, English on the ball • This list is meant to get the big things out on the table and also give you ideas for a development plan. Development Plan: Iteration Take a step back and think about the development process and your goals. Game development lends itself well to an iterative approach. Build something small and work to improve it in steps. Consistent forward progress depends on choosing the size and direction of your iterative steps. Games are built by combining a number of individual technologies. The set of technologies you must pull together depend on the game, the engine, and the development environment. Iteration steps should be small and able to be completed in one sitting. In the beginning of a project, find steps that result in visual changes. Start by getting the main game objects drawn. a hello world window • overhead camera and drawing the court • draw the ball • draw the paddles • Next, do one step with all the different tech your game needs. The goal is to write code that can be improved on. The first part of a project focuses on a kind of breadth-first development. Keep your steps small -- aim for something you will complete in one sitting. player input and connect it to the paddles • collisions of the ball with the court • collisions of the ball with the player paddles • playing of a sound • 2D heads up display of score • It's important not to wait too long before tackling this next stage. Setup your game to run in an attract mode loop, play the game, and exit without any memory leaks. write a game controller that manages state transitions including the following • display a title screen • wait for start button to start the game • start and play the game until someone wins • display the game over screen • testing for clean, balanced coding • loop until the player exits • exit without any memory leaks • After the first pass of your game elements and technology is completed, you will likely have some messy code. Now is a good time to look at the overall structure of your game and decide if you can improve it. Also, look at each system, improving the names usually leads to better code.
  3. 3. improve the structure • improve the names, define and follow a coherent approach • Now, focus on the game itself. Iterate on the elements that stick out the most. Eventually, your focus will change and instead of fixing things that are problems, you'll be improving things and looking for ways to make things more interesting. find the worst thing, iterate on it until it's not the worst thing • find things that you can make better • Realize that you will never run out of things to work on, but you will run out of time. Developing a Plan: Refactoring Plan on refactoring your code from the start. Rather than work to write perfect code, write good code that can be refactored. This means that you should write code that with distinct names. Set yourself up to be able to search and rename sections of code. Also, write code that groups a feature together. Refactor the sections when they get too big or the logic starts getting tangled or they clearly belong in their own function. It seems that most code is refactored two or three times during a project. However, don't be over anxious to refactor code, you should wait to refactor until the old code becomes unbearable. The Game Loop The beginning of the game controller class. Independent update and draw. Keep features orthogonal. Class pointers and references Pointers are an important part of game programming. Get used to using them. Common Identifier Space Start all of your identifiers at one. Reserve zero for defaults and uninitialized tests. Define one set of identifiers. Use these for game ids, return codes, other uses. enum { // int, unique ids for any use in the game // rather than have ids all over the place GAMEID_TMP=1, // reserved GAMEID_GAME, // game instance // game modes GAMEID_MODE_ATTRACT, GAMEID_MODE_GAME, // return codes GAMEID_BALL_REFLECT, GAMEID_BALL_OFFLEFT, GAMEID_BALL_OFFRIGHT, };
  4. 4. Function construction Initialize. Body. Finalize and exit. One return. Initialization section must initialize all the objects. Design your objects so zeroing them initializes them. Return negative values for errors. int function() { int r; ///////////// // initialize /////// // body r=0; // normal exit code, success BAIL: ///////////////////// // finalize, clean up return r; } // function() Free what you allocate Clean up after yourself. Write your final code so that it is path independent. If non-zero, free and zero. Public Classes Projects go through a few phases. In the beginning, setup your code for flexibility. The beginning of a project is marked by changes in direction and structure. One way to facilitate this is with classes with all the elements marked public. Once you understand the problem space and your solution, you will refactor your code to move fields into the private or protected. Until then, it will only slow you down. Protecting fields becomes important when you scale your project up and add programmers. Be sure to set aside time for this step. Feature based accessors, not straight-to-vars. Peter Bennett. Allocating and Freeing Objects In C, memory allocated and freed with the malloc() and free() functions and objects must be setup by the programmer. In C++, the new operator calls malloc() to allocate memory and calls the constructor function to setup the object. Likewise, the delete operator calls the destructor function to cleanup the object and then calls free() to release the memory. Memory is one of your primary resources. Game objects are defined by classes and take up memory when they are created.
  5. 5. QE Base Classes Derive from qe base classes so all the allocations run through the engine. Objects derived from qe base classes zero their memory when allocated. Derive from qeUpdateBase for objects that are managed by the game engine. Normally, you will override the update() and draw() functions with your own code. Derive from qe for simple, non-managed objects. Bracketing Resources Bullet-proof your resource allocation and freeing. Build a structure that guarantees that you free all the resources you allocate. This is accomplished with a 3-part structure: initialization, use, and finalization. The initialization is code that is guaranteed to run early in the game, and finalization code is guaranteed to run before you exit. Initialization code should simply zero all resource fields. The finalization code, which is also guaranteed to run, checks resource fields, frees any non-zero resource, and then zeros that field. There must be only one owner of each resource and one initialization and finalization section of code for each resource field. The code that runs in-between the initialization and finalization must follow the simple rules to test the resource field before each use and set the field if the resource is successfully created. This structure allows you to bracket your resource usage and provides the necessary structure to write code that guarantees balanced resource usage. initialization (guaranteed): zeros • finalization (guaranteed): tests, frees & zeros • usage: tests, allocates & sets, uses • Game Controller Class This class manages the game. Load all the initial content including images and sounds. Modes should be avoided because they promote structural discontinuity and ultimately complicate your program greatly. However, modes do make prototyping easier and novice programmers should use modes until a modeless structure resolves itself. Start off in attract mode. When the start button is pressed, drop into game mode. Attract mode waits for the start button and then drops into game mode. Display the last scores. Game mode runs the game until the game is over. Then drops back into attract mode. Matrices A matrix is a set of numbers that define any combination of movements, rotations, and scaling of objects in a 3D world. The matrices used here are 12 element matrices. The first 3 entries are the X, Y, and Z positions. The next 9 entries define a simple rotation and scaling matrix. Taken together, the 12 elements are all you need for most game operations and qe provides a set of functions to operate on the raw 12 element array of floats.
  6. 6. Simple 3D Camera For now, it's best to use the code and experiment with it as you go. Cameras are a software metaphor implemented with matrices. The camera matrix transforms objects from world space into camera space. In other words, it treats the camera as the center of the world, and moves and rotates everything in the world in front of the camera. In OpenGL, this matrix is called the MODELVIEW matrix. There are two matrices in OpenGL. You can think of the MODELVIEW matrix as the way you position the camera, and the PROJECTION matrix as the camera's lens. OpenGL's PROJECTION matrix transforms objects from camera space into clip coordinates. The perspective division (divide by z) then transforms clip coordinates into normalized device coordinates. These coordinates are then mapped through the viewport into a window. BRK(), Code to Continue This macro expands to into an interrupt call that breaks the program execution and 'wakes up' the debugger at that point. If the debugger is not running, the macro has no effect. Write your code to detect errors and continue running. Players don't want to see a dialog box asking them if they want to send an error report to Microsoft. Even in extreme cases, do your best to continue running. That said, you must detect and handle all errors. When you run into an error, report it, and write code to do the best it can to continue running -- this may mean an object is drawn without a texture or may even be missing. Drawing The Court The court is managed by the world class. A solid rectangle is drawn for the court. // JFL 14 Aug 07 // JFL 15 Mar 09 int World::draw(void) { glColor3f(0,0,1); // set color to blue glPolygonMode(GL_FRONT,GL_FILL); // draw filled polygons glBegin(GL_QUADS); // draw quads counter-clockwise from camera's view glVertex3f(this->xyzMin[0],this->xyzMin[1],this->xyzMin[2]); glVertex3f(this->xyzMin[0],this->xyzMin[1],this->xyzMax[2]); glVertex3f(this->xyzMax[0],this->xyzMin[1],this->xyzMax[2]); glVertex3f(this->xyzMax[0],this->xyzMin[1],this->xyzMin[2]); glEnd(); return 0; } // World::draw() Reset Flags Once Flags can be used to signal a request from many possible places. Handle the flag in one place. Test the flag, clear it, call the handler. Clear the flag in only one section of code.
  7. 7. 000ZY Coordinates This choice is largely arbitrary, but should be something you're comfortable with. One unit = one foot is the standard I use. This depends on the scale of your game, but should be determined before you start coding. Velocities Variable frame rate systems must multiply the velocity by the amount of time elapsed since the last loop. Keep track of the time since the last update. float t; // find time since last update t=this->timeOfLastUpdate; this->timeOfLastUpdate=qeTimeFrame(); t=this->timeOfLastUpdate-t; // delta // xyz += vel*t this->xyz[0]+=this->vel[0]*t; this->xyz[1]+=this->vel[1]*t; this->xyz[2]+=this->vel[2]*t; This update method -- moving by adding the velocity times the elapsed time -- is called Euler integration. The timeOfLastUpdate variable must be reset when the object is reset. 3D World, Camera The world is quot;in 3Dquot;. However, the camera is fixed above it and looking down. This gives the appearance of a 2D game. ////////////////////// // Camera class fields float fovyHalfRad; // field of view angle in y direction in radians float nearClip; // near clipping plane float farClip; // far clipping plane float winWidth; // in pixels float winHeight; // in pixels float winWDivH; // window aspect ratio float nearHeight; // height of window at near plane float mat12[12]; // camera matrix /////////////////////// // Camera setup -- once this->nearClip = 1; this->farClip = 500; this->fovyHalfRad = 0.5*((63*PI)/180.0); // 0.5*(degrees->radians) this->nearHeight = this->nearClip * MathTanf(this->fovyHalfRad); // camera matrix transforms from world space into camera space SET3(pos,0,CAMERA_Y,0); // position of camera SET3(at,0,0,0); // where camera is looking at SET3(up,0,0,-1); // the camera's up direction qeCamLookAtM12f(this->mat12,pos,at,up); // compute camera mat
  8. 8. ///////////////////////////////////////////////////// // Camera matrices -- before you draw with the camera if(qeGetWindowSize(&this->winWidth,&this->winHeight)<0) bret(-2); this->winWDivH=this->winWidth/this->winHeight; // set the PROJECTION matrix (the camera lens) glMatrixMode(GL_PROJECTION); glLoadIdentity(); float yy = this->nearHeight,xx=this->nearHeight*this->winWDivH; glFrustum(-xx,xx,-yy,yy,this->nearClip,this->farClip); // set the MODELVIEW matrix (position and orientation of the camera) glMatrixMode(GL_MODELVIEW); glLoadIdentity(); qeGLM12f(this->mat12); // set matrix 2D Gameplay The keyboard and joystick are used for the player's up and down. Keyboard as Buttons Input By default, the keyboard acts like a set of buttons to the game engine. QE uses button counts which are incremented when the buttons are pressed and released. When the button is down, the count is odd, and when the button is up, the count is even. The bottom bit is set for odd numbers and can easily be tested with a bitwise test. // up-down-left-right arrow mapping if(1&qeInpButton(QEINPBUTTON_UP)) /* up button is pressed */; else if(1&qeInpButton(QEINPBUTTON_DOWN)) /* down button is pressed */; Joystick Input Joystick positions are returned as floating point values. Some care should be taken to drop out the values when the joystick is near the center. // get player 0 joystick value -1..0..1 stick=qeInpJoyAxisf(0,QEJOYAXIS_LEFT_Y); #define STICK_DEADZONE 0.2 // enforce stick deadzone & re-normalize if(stick>STICK_DEADZONE) stick=(stick-STICK_DEADZONE)/(1.0-STICK_DEADZONE); else if(stick<-STICK_DEADZONE) stick=(stick+STICK_DEADZONE)/(1.0-STICK_DEADZONE); else // in deadzone, zero stick=0; Sounds Record sounds into .wav files. Most sound effects should be recorded in mono at a low resolution. Trigger the sounds in the game.
  9. 9. // setup sound quot;bumpquot; on channel 1 if((r=qeSndNew(quot;bumpquot;,M_SNDNEW_CH_1,0,quot;art/sounds/pongbump.wavquot;))<0) BRK(); // trigger the sound qeSndPlay(quot;bumpquot;); Use Restart Functions Use restart rather than start functions. Names matter. Start functions usually hide assumptions regarding the state of the game. Drawing 2D: Camera and Image Use the 32 bit image of the digits. The image has been created so there are 8 digits on the first row and 2 on the second row. 0 1 2 3 4 5 6 7 and 8 9. // register image if(qeImgNew(quot;icons1quot;,0,quot;art/images/icons1.tgaquot;)<0) BRK(); // make sure path is correct qeTexBind(quot;icons1quot;); 2D images are drawn the same way other polygons are drawn. However, the camera and draw modes have to be setup correctly. qefnSysCamFlat(); // ortho: (0,0) top left (1,1) bottom right // setup texture mode to blend glPolygonMode(GL_FRONT,GL_FILL); glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_ADD); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); Drawing 2D: UVs and Vertices As always, vertex coordinates are specified in the space defined by the camera matrices and define the structure of the object. UV coordinates define how the image maps from texture space to the polygon. UVs are specified in normalized image space. Ways to get the images to stretch or be drawn close to one to one from the image. To find UVs, start with the image coordinates and divide by the image size. // find image coordinates for a positive integer n // icons in our image are 32x32 and there are 8 across horizontally // these coordinates are in pixel coordinates of the image // top left of the image 0,0 u0=(n&7)*GAME_ICON_WIDTH; v0=(n>>3)*GAME_ICON_HEIGHT; // map image coordinates into uv space u0/=imgWidth; v0/=imgHeight;
  10. 10. Name Your Objects Debugging is easier when object have string names. When debugging, checking the names of objects is a good way to verify system and game integrity. Also, you can sometimes trap on on the name.