Gdc09 Minigames

Preview:

DESCRIPTION

 

Citation preview

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

Recommended