Post on 22-Jan-2015
description
transcript
Mini GamesLessons From Rebuilding Classic Games in C++ and OpenGL
Joe LinhoffEugene JarvisDarren Torpey
MiniGamesRebuilding Three Classic
Joe LinhoffEugene JarvisDarren Torpey
DePaul University
BS Game Development since 2004 programming production and design
MS Game Development Animation, Computer Science, Software
Engineering, Digital Cinema Outstanding Faculty
Eugene Jarvis (Game Designer In Residence) Alexander Seropian (Halo, future GDIR) William Muehl, Ed Keenan, Patrick Curry, Alan
Turner
Workshop Target
This is a discussion on the teaching of game development and will focus on the use of mini-games to teach game programming and game
design.
There will be no art.
Modern Game Design
1) Simulate Everything 2) The most realistic shit ever 3) goto 1;
Running A Game Dev Class
Infrastructure
Tools students and instructors use same tool set
Directory Structure bad paths kill projects
Server what kind of support you need
Communication Channels define them
Infrastructure
Tools MSVS C++ Express Edition TortoiseSVN (Subversion) IM (Skype)
Directory Structure explained in other slides
Server Subversion Wiki
Communication Channels (manage or drown) avoid email to students: SVN wiki from students: SVN Skype
Setup
Setup is critical Too many variables for students to do this Setup includes directory structure Must give working starter kits
SSID: "joshua weinberg mac bookpro 17" www.joeco.com/qe.htm
Setup
DirectX runtime Google and install lastest runtime from Microsoft's
site -- needed for sound and input MSVS C/C++ Express Edition
Version Control(highly recommended)
Distribution you setup the
directory structure you populate the files
Help students commit files
and can IM you for real-time help
Collection commit their work time stamped
TortoiseSVN easy to use works well SVN command line
tool for scripting free
Problems too much committed too little committed
Directory Structure
dev -- development root can exist anywhere class1 student1 student2 class2 student1 student2 projects gdc09 <-- $(SolutionDir) art mini -- code files for mini-games qeStartup.c m_minipong.cc mini.vcproj mini.vcproj.user <-- user properties default game.sln bin -- shared bin files qeblue.dll freeglut.dll inc -- shared headers for qe and freeglut qe.h qec.h qefn.h GL/glut.h GL/freeglut.h ... lib -- shared lib qeblue.lib freeglut.lib
MSVC Properties: paths
Set in all Configurations
Debugging Working Directory: $(SolutionDir) Environment: path=$(SolutionDir)../../bin;%path%C/C++ General Additional Include Directories: $(SolutionDir)../../incLinker General Additional Library Directories: $(SolutionDir)../../lib Input Additional Dependencies: qeblue.dll
set in project properties instead of Project and Solution Options
inherited properties may also provide good solution
mini.vcproj.user
this is the 'short' version of the user file mini.vcproj.D630.Joe.user is long version (includes
machine and user name) used if long version isn't found all modifications are saved to the long version confusing and not useful to commit the long
version since it only works on one machine lesson1: make changes and commit short
version lesson2: always test on a different system
Hello World
Building Hello World
should be able to download, launch the solution file, build, and run
config.h
one project build one program a time
// config.h#ifndef CONFIG_H#define CONFIG_H
// build one at a time#define BUILD_HELLO 1 // hello world#define BUILD_MINIPONG 0 // pong#define BUILD_MINIMISSILE 0 // missile command#define BUILD_MINIROBO 0 // robotron
#endif // ndef CONFIG_H
// Copyright (C) 2007-2009 Joe Linhoff, All Rights Reserved// m_hello.c#include "config.h" // include the config file first#if BUILD_HELLO // compile this app#include "qe.h" // engine include file
// qeMain()int qeMain(int argc,chr *argv[]){ qePrintf("%s / %s / %s\n",__FILE__,glGetString(GL_VERSION),qeVersion());
qePrintf("Hello World\n");
// turn control over to the engine until the user closes the program qeForever();
return 0;} // qeMain()
#endif // compile this app// EOF
QE
lightweight academic game engine written in C, supports C++ OpenGL see reference
Pong, 1972
Teaching Game Development Starting Student Projects
Research and brainstorm Create "1000 Features" list Choose coordinates Draw screenshot and world map Start very small iterations
limit scope of iteration to one sitting get something running in first five minutes bias work in beginning toward visual changes, then
input, core mechanic keep it working, always be improving
Start with programmer art Write clean code, bracket resources Refrain from refactoring until you can't stand it Always plan for the future but code for today
Game Development
Process workflow
Design what are you trying to do
Development tools and language build an exe
Game development techniques solutions to the problem space
1000 Features (handout)unique value 0..1000
possible feature for your game -- focus on what you see, hear, and how to get it on the screen
000ZY Coordinates
Right handed coordinate system Root for all models is (0,0,0) Z is forward Y is up All translation, rotation, scale match Maya
i.e. given TRS, build matrix such that object draws like it does in Maya
Choose units one unit is one foot
Camerassoftware metaphor
// JFL 03 Oct 08class Camera : public qe {public: chr name[16]; // name float fovyHalfRad; // 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 at near plane float mat12[12]; // camera matrix int draw(); // draw-step function Camera(chr *name); // constructor}; // class Camera
// setup -- happens once in mini-pongthis->nearClip = 1;this->farClip = 500;this->fovyHalfRad = 0.5*((63*PI)/180.0);this->nearHeight = this->nearClip * MathTanf(this->fovyHalfRad);
// camera matrix -- from world space into camera spaceSET3(pos,0,CAMERA_Y,0); // position of cameraSET3(at,0,0,0); // where camera is looking atSET3(up,0,0,-1); // the camera's up directionqeCamLookAtM12f(this->mat12,pos,at,up); // camera mat
// draw -- set every frame before you drawif(qeGetWindowSize(&this->winWidth,&this->winHeight)<0) bret(-2); // jump to function exitthis->winWDivH=this->winWidth/this->winHeight;
// set the PROJECTION matrix (the camera lens)glMatrixMode(GL_PROJECTION);glLoadIdentity();float yy = this->nearHeight;float xx=this->nearHeight*this->winWDivH;glFrustum(-xx,xx,-yy,yy,this->nearClip,this->farClip); // MODELVIEW (position and orientation of the camera)glMatrixMode(GL_MODELVIEW);glLoadIdentity();qeGLM12f(this->mat12); // set matrix
OpenGL 4x4 Matrices (M16)
#define VecTransformM16(_d_,_v_,_m_) \ // d=dstvec v=srcvec m=mat16 (_d_)[0]=(_v_)[0]*(_m_)[M16_11]+(_v_)[1]*(_m_)[M16_21] \ +(_v_)[2]*(_m_)[M16_31]+(_m_)[M16_X], \ (_d_)[1]=(_v_)[0]*(_m_)[M16_12]+(_v_)[1]*(_m_)[M16_22] \ +(_v_)[2]*(_m_)[M16_32]+(_m_)[M16_Y], \ (_d_)[2]=(_v_)[0]*(_m_)[M16_13]+(_v_)[1]*(_m_)[M16_23] \ +(_v_)[2]*(_m_)[M16_33]+(_m_)[M16_Z]
#define VecRotM16(_d_,_v_,_m_) \ (_d_)[0]=(_v_)[0]*(_m_)[M16_11]+(_v_)[1]*(_m_)[M16_21] \ +(_v_)[2]*(_m_)[M16_31], \ (_d_)[1]=(_v_)[0]*(_m_)[M16_12]+(_v_)[1]*(_m_)[M16_22] \ +(_v_)[2]*(_m_)[M16_32], \ (_d_)[2]=(_v_)[0]*(_m_)[M16_13]+(_v_)[1]*(_m_)[M16_23] \ +(_v_)[2]*(_m_)[M16_33]
float mat[16]; glGetFloatv(GL_MODELVIEW_MATRIX,mat);
QE 3x4 matrices (M12)non-standard: XYZ and 3x3 rotation matrix
#define VecTransformM12(_d_,_v_,_m_) \ (_d_)[0]=(_v_)[0]*(_m_)[M12_11]+(_v_)[1]*(_m_)[M12_21] \ +(_v_)[2]*(_m_)[M12_31]+(_m_)[M12_X], \ (_d_)[1]=(_v_)[0]*(_m_)[M12_12]+(_v_)[1]*(_m_)[M12_22] \ +(_v_)[2]*(_m_)[M12_32]+(_m_)[M12_Y], \ (_d_)[2]=(_v_)[0]*(_m_)[M12_13]+(_v_)[1]*(_m_)[M12_23] \ +(_v_)[2]*(_m_)[M12_33]+(_m_)[M12_Z]
#define VecRotM12(_d_,_v_,_m_) \ (_d_)[0]=(_v_)[0]*(_m_)[M12_11]+(_v_)[1]*(_m_)[M12_21] \ +(_v_)[2]*(_m_)[M12_31], \ (_d_)[1]=(_v_)[0]*(_m_)[M12_12]+(_v_)[1]*(_m_)[M12_22] \ +(_v_)[2]*(_m_)[M12_32], \ (_d_)[2]=(_v_)[0]*(_m_)[M12_13]+(_v_)[1]*(_m_)[M12_23] \ +(_v_)[2]*(_m_)[M12_33]
Velocities
variable frame rates float qeTimeFrame()
returns seconds since engine start / restart
keep track of the time since the last update
use Euler integration
// JFL 25 Jan 09class Ball : public qe {public: chr name[16]; // name float timeOfLastUpdate; // in seconds float xyz[3]; // current float vel[3]; // velocity Ball(chr *name); // constructor int update(); // update function int draw(); // draw function}; // class Ball
// update, move the ballfloat t;
// find time since last updatet=this->timeOfLastUpdate;this->timeOfLastUpdate=qeTimeFrame();t=this->timeOfLastUpdate-t; // delta
// xyz += vel*tthis->xyz[0]+=this->vel[0]*t;this->xyz[1]+=this->vel[1]*t;this->xyz[2]+=this->vel[2]*t;
Collisions
simplifications move, then collide
non-moving objects don't worry about
resolution order run through once
guarantee after detection, move
objects out of that collision (may be in another -- too bad)
end up in valid world position
no movement after collision resolution
Collisions ball v world
if over right or left score point, re-serve
if over top or bottom set to top or bottom reflect (flip z vel)
paddle v world make sure player
stays on the court ball v paddles
test against near edge of paddle set to near edge bounce (flip x vel) add English (later)
improvements preserve distance
when colliding don't just set to
collision edge reflect at collision point
does order matter? theoretically unlikely
fast balls could run through the
paddle depends on paddle
size and ball speed really need to handle
moving collisions
Game ControllerUse Singleton Pattern
manage the game loop with one Game instance
good chance to use the Singleton pattern
Game *Game::instance=0; // initialize Singleton
// JFL 13 Aug 08Game::Game(chr *name) : qeUpdateBase(name,0,GAMEID_GAME){ // constructor this->name = qeObjName(this->_oShared); // get name} // Game::Game()
// JFL 16 Aug 08void Game::InstanceDelete(){ if(Game::instance) Game::instance->objRemove(); // request obj removal } // GameInstanceDelete()
// JFL 16 Aug 08Game* Game::InstanceNew(){ if(!Game::instance) Game::instance = new Game("game1"); return Game::instance;} // Game::InstanceNew()
// JFL 16 Aug 08Game* Game::InstanceGet(){ return Game::instance;} // Game::InstanceGet()
// JFL 03 Oct 08class Game : public qeUpdateBase { // game controller record chr *name; // points to system name static Game *instance; // singleton Game(chr *name); // constructorpublic: static Game* InstanceNew(); static Game* InstanceGet(); static void InstanceDelete();
}; // class Game
qeUpdateBase base class
engine base class provides virtual
update() draw() final()
adds to list of engine objects
all objs derived from qeUpdateBase update() functions
called before any of the draw() functions
qe base class
derive from qe for simple objects
no overhead runs through engine's
memory system keeps count to keep
you honest zeros memory on
allocation
Game Superstructurevisualization
Button Countsuns qeInpButton(uns inpb); // QEINPBUTTON_
by default the keys are mapped as buttons
every up and down transition, the engine adds a value to that count
single button count value gives state and history
if odd ==> down if(b&1) /* down */;
store count, come back later and find how many transitions good for coins
Joysticksfloat qeInpJoyAxisf(uns joy,uns axis); // use QEJOYAXIS_
uns qeInpJoyButton(uns joy,uns button); // use QEJOYBUTTON_
joysticks start at 0 OK to test even if stick
is not present the axis is defined qeInpJoyAxisf()
returns values -1..1 mind the DEADZONE
Draw simple filled rectangle
glColor3f(1,1,1); glPolygonMode(GL_FRONT,GL_FILL); // draw filled polygons glBegin(GL_QUADS); // draw quads counter-clockwise from camera's view glVertex3f(-1,0,-3); glVertex3f(-1,0,3); glVertex3f(2,0,3); glVertex3f(2,0,-3); glEnd();
Loading and Playing Sounds
capture sounds low-res, mono for
effects wav files register play
// setup sound "bump" on channel 1if((r=qeSndNew("bump",M_SNDNEW_CH_1,0,"art/sounds/pongbump.wav"))<0) BRK();
// trigger the soundqeSndPlay("bump");
qePrintf()qeLogf()
printf-like function calls to qePrintf() get
added to log file qelog.txt
add to log file directly with qeLogf()
BRK()
normal asserts() kill the game -- this can be bad
code with BRK() to continue running
the "break" goes away when outside the debugger
Bracket Resources
bullet-proof allocation and freeing of resources memory file-handles etc
usually two ways to kill normal object life program abort
good solution initialization
guaranteed to run clear all fields
body finalization
guaranteed to run can be triggered when
body finishes normally or w/abort
Missile Command, 1980
Strings 'chr' in qebase.h defines an 8 bit character for
internal programming use guarantees & principles
sizes are always byte sizes of whole buffers zero termination guaranteed if dstsize>0 pointer-terminated and zero-terminated strings
much faster, safer pointer-terminator always option, pass 0
must be zero-terminated sz* functions defined qebase.h int szcpy(chr *dst,int dstsize,chr *ss,chr *sx);
ss is string start, sx is pointer-terminator or 0 int szfmt(chr *dst,int dstsize,chr *fmt,...);
printf-like fmt
LLNode
Simple doubly linked list node
Type field t specifies game-specific type
Type field is zero for list head
// linked listtypedef struct _llnode { struct _llnode *next; struct _llnode *prev; int t; // type: listhead=0, others=non-zero} LLNode;
// JFL 23 Aug 06// JFL 20 Mar 08; re-worked from DLvoid LLMakeHead(LLNode *h){ h->next=h->prev=h; h->t=0;} // LLMakeHead()
// JFL 20 Mar 08; re-worked from DL// JFL 18 May 08; link to selfvoid LLMakeNode(LLNode *n,int t){ n->next=n->prev=n; n->t=t;} // LLMakeNode()
// JFL 23 Aug 06// JFL 20 Mar 08; re-worked from DLvoid LLLinkAfter(LLNode *h,LLNode *n){ n->next=h->next; n->next->prev=n; n->prev=h; h->next=n;} // LLLinkAfter()
// JFL 05 May 06// JFL 20 Mar 08; re-worked from DLvoid LLLinkBefore(LLNode *h,LLNode *n){ n->prev=h->prev; n->prev->next=n; n->next=h; h->prev=n;} // LLLinkBefore()
// JFL 05 May 06// JFL 20 Mar 08; re-worked from DLvoid LLUnlink(LLNode *n){ n->prev->next=n->next; n->next->prev=n->prev; n->next=n->prev=n; // multiple unlinks OK} // LLUnlink()
Robotron, 1982