You are on page 1of 7

RapidGameDevelopmentInPython

RapidGameDevelopmentInPython
Author:RichardJones

Howdoyoumakeagame?
Gamesconsistlargelyofuserinput,gameoutputandsomesortofworldsimulation.Themostinteresting
gamesaretheonesthatdosomethingdifferentornewinthesimulation.Pythonisareallynicelanguagefor
writinggamesimulationsin.Fortunately,otherpeoplehavedoneareallyexcellentjobofprovidingPython
librariesforuserinputandgameoutput.

Pythonhas...
Python
Possiblythebestlanguageforwritinggameworldsimulationsin.It'scleartoreadandwrite,easyto
learn,handlesalotofprogramminghousekeepingandisreasonablyfast.
PyGame
Providesuserinputhandling(mouse,keyboard,joystick)andgameoutputviascreen(shapedrawing,
imageblitting,fontrendering)andspeakers(effectsandmusic).Strictly2dgraphics.
PyOpenGL
GivesPythonthepowerofOpenGLforveryhighperformance3dgraphics.
(Soya3d,PGU,...)
Additionallibrariesoverthetopoftheabovethatprovideasolidbasisforyourworldsimulation,
gamemapdrawingcode,modelloading,eventmanagement,etc.

Managingthescreenthebasics
Basicscreeninitialisationlookslike:
>>>frompygame.localsimport*
>>>screen=pygame.display.set_mode((1024,768))
>>>screen=pygame.display.set_mode((1024,768),FULLSCREEN)

You can call set_mode duringyourgameto switchfromwindowed(thedefault) tofullscreen.Other


displaymodeflags(youjust|themtogether):
DOUBLEBUF
mustbeusedforsmoothanimation
OPENGL
letsyoudraw3dsceneswithPyOpenGL,butwon'tletyouperformmostpygamedrawingfunctions.
Thereisanoptionalbitdepthflag,butit'salmostalwaysbettertonotincludeitandgowithwhatever
platformdefaultisavailable.
Ifyou'reusingDOUBLEBUFthenyou'llneedtoflipthescreenafteryou'verenderedit.Thisisassimpleas:
>>>pygame.display.flip()

Drawingacar
We'regoingtodrawacaronscreen.Wedothisusingoneofthemostimportantdrawingprimitives,the
BLIT(BlockImageTransfer).Itcopiesanimagefromoneplace(eg.yoursourceimage)toanotherplace
(eg.thescreenatX=50,Y=100):
84OpenSourceDevelopers'Conference2005

RapidGameDevelopmentInPython
>>>car=pygame.image.load('car.png')
>>>screen.blit(car,(50,100))
>>>pygame.display.flip()

Atthispoint,thecarshouldappearonthescreenwithitstopleftcornerpositionedat(50,100).Wealways
startcountingXcoordinatesfromtheleft,andYcoordinatesfromthetopofthescreen.
Wecanalsorotateimages:
>>>importmath
>>>rotated=pygame.transform.rotate(car,45*math.pi/180)
>>>screen.blit(car,(50,100))
>>>pygame.display.flip()

Animatingthecar
Animatinganythingonscreeninvolvesdrawingascene,clearingitanddrawingitagainslightlydifferently:
>>>foriinrange(100):
...screen.fill((0,0,0))
...screen.blit(car,(i,0))

Animationalsoconsistsofdoingthatprettyquickly.
Notealsothatclearingandredrawingascreenisquiteanunoptimalwayofanimating.It'susuallybetterto
updatethepartsofthescreenthathavechangedinstead.Sprites,mentionedlater,helpusdothis.

Inputhandling
There'sanumberofwaystogetusereventsinPyGame,themostcommonofwhichare:
>>>importpygame
>>>pygame.event.wait()
>>>pygame.event.poll()
>>>pygame.event.get()

waitwillsitandblockfurthergameexecutionuntilaneventcomesalong.Thisisnotgenerallyveryuseful
forgames,asyouwanttobeanimatingthingsatthesametime.pollwillseewhetherthereareanyevents

waitingforprocessing.Ifthere'snoevents,itreturnsNOEVENTandyoucandootherthings.Thefinal
form,get,islikepollexceptthatitreturnsallofthecurrentlyoutstandingevents(youmayalsofilterthe
eventsitreturnstobeonlykeypresses,ormousemoves,etc.)
Ishouldalsomentiontiminghere,asit'simportanttotheuserexperience.Withouttimingcontrol,your
gamewillrunasfastasitpossiblycanonwhateverplatformithappenstobeon.Timingcontroliseasyto
add:
>>>clock=pygame.time.Clock()
>>>FRAMES_PER_SECOND=30
>>>deltat=clock.tick(FRAMES_PER_SECOND)

tickinstructstheclockobjecttopauseuntil1/30thofasecondhaspassedsincethelastcalltotick.This
effectivelylimitsthenumberofcallsto tick to30persecond.The actual timebetween tick callsis

returned(inmilliseconds)onslowercomputersyoumightnotbeachieving30tickspersecond.
It'simportanttonotethatthe30framespersecondwillalsodeterminehowoftenyourgamerespondstouser
input,asthatischeckedatthesametimethatthescreenisdrawn.Checkingforuserinputanyslowerthan30
framespersecondwillresultinnoticeabledelaysfortheuser.
30timesasecondisareasonablenumbertoaimforoureyesgenerallywon'tbenefitfromanythingabove
OpenSourceDevelopers'Conference200585

RapidGameDevelopmentInPython
30.Ifyourgameisactionoriented,youmaywishtoaimfordoublethatsothatplayersfeeltheirinputis
beingprocessedinareasonablyresponsivemanner.

Bringingtogethersomeelements
Thefollowingcodewillanimateourlittlecaraccordingtousercontrols.Itconsistsbroadlyoffoursections
(initialisation,userinput,animationandrendering):
#INTIALISATION
importpygame,math,sys
frompygame.localsimport*
screen=pygame.display.set_mode((1024,768))
car=pygame.image.load('car.png')
clock=pygame.time.Clock()
k_up=k_down=k_left=k_right=0
speed=direction=0
position=(100,100)
TURN_SPEED=5
ACCELERATION=2
MAX_FORWARD_SPEED=10
MAX_REVERSE_SPEED=5
BLACK=(0,0,0)
while1:
#USERINPUT
clock.tick(30)
foreventinpygame.event.get():
ifnothasattr(event,'key'):continue
down=event.type==KEYDOWN#keydownorup?
ifevent.key==K_RIGHT:k_right=down*5
elifevent.key==K_LEFT:k_left=down*5
elifevent.key==K_UP:k_up=down*2
elifevent.key==K_DOWN:k_down=down*2
elifevent.key==K_ESCAPE:sys.exit(0)#quitthegame
screen.fill(BLACK)
#SIMULATION
#..newspeedanddirectionbasedonaccelerationandturn
speed+=(k_up+k_down)
ifspeed>MAX_FORWARD_SPEED:speed=MAX_FORWARD_SPEED
ifspeed<MAX_REVERSE_SPEED:speed=MAX_REVERSE_SPEED
direction+=(k_right+k_left)
#..newpositionbasedoncurrentposition,speedanddirection
x,y=position
rad=direction*math.pi/180
x+=speed*math.sin(rad)
y+=speed*math.cos(rad)
position=(x,y)
#RENDERING
#..rotatethecarimagefordirection
rotated=pygame.transform.rotate(car,direction)
#..positionthecaronscreen
rect=rotated.get_rect()
rect.center=position
#..renderthecartoscreen
screen.blit(rotated,rect)
pygame.display.flip()

Morestructure
Sotheabovecodeisalittleadhoc.Mostgameswillwanttoorganisethingstobeabletobettercontrol
86OpenSourceDevelopers'Conference2005

RapidGameDevelopmentInPython
simulationandrendering.Todothis,wecanusesprites.Aspriteholdsanimage(e.g.acar)andinformation
aboutwherethatimageshouldbedrawnonscreen(i.e.itsposition.)Thisinformationisstoredonthesprite's
imageandrectattributes.
Spritesarealwaysdealtwithingroups - evenifagrouponlyhasoneSprite.Spritegroupshavea draw
methodwhichdrawsthegroup'sspritesontoasuppliedsurface.Theyalsohaveaclearmethodwhichcan
removetheirspritesfromthesurface.Theabovecarcoderewrittenusingasprite:
#INTIALISATION
importpygame,math,sys
frompygame.localsimport*
screen=pygame.display.set_mode((1024,768))
clock=pygame.time.Clock()
classCarSprite(pygame.sprite.Sprite):
MAX_FORWARD_SPEED=10
MAX_REVERSE_SPEED=10
ACCELERATION=2
TURN_SPEED=5
def__init__(self,image,position):
pygame.sprite.Sprite.__init__(self)
self.src_image=pygame.image.load(image)
self.position=position
self.speed=self.direction=0
self.k_left=self.k_right=self.k_down=self.k_up=0
defupdate(self,deltat):
#SIMULATION
self.speed+=(self.k_up+self.k_down)
ifself.speed>self.MAX_FORWARD_SPEED:
self.speed=self.MAX_FORWARD_SPEED
ifself.speed<self.MAX_REVERSE_SPEED:
self.speed=self.MAX_REVERSE_SPEED
self.direction+=(self.k_right+self.k_left)
x,y=self.position
rad=self.direction*math.pi/180
x+=self.speed*math.sin(rad)
y+=self.speed*math.cos(rad)
self.position=(x,y)
self.image=pygame.transform.rotate(self.src_image,self.direction)
self.rect=self.image.get_rect()
self.rect.center=self.position
#CREATEACARANDRUN
rect=screen.get_rect()
car=CarSprite('car.png',rect.center)
car_group=pygame.sprite.RenderPlain(car)
while1:
#USERINPUT
deltat=clock.tick(30)
foreventinpygame.event.get():
ifnothasattr(event,'key'):continue
down=event.type==KEYDOWN
ifevent.key==K_RIGHT:car.k_right=down*5
elifevent.key==K_LEFT:car.k_left=down*5
elifevent.key==K_UP:car.k_up=down*2
elifevent.key==K_DOWN:car.k_down=down*2
elifevent.key==K_ESCAPE:sys.exit(0)
#RENDERING
screen.fill((0,0,0))
car_group.update(deltat)
car_group.draw(screen)
pygame.display.flip()

OpenSourceDevelopers'Conference200587

RapidGameDevelopmentInPython
Notethatmostlythecodehasjustbeenmovedaroundalittle.Thebenefitofspritesreallycomesinwhen
youhavealotofimagestodrawonscreen.
PyGamespriteshaveadditionalfunctionalitythathelpusdeterminecollisions.Checkingforcollisionsis
reallyprettyeasy.Let'sputsomepadstodriveoverintothesimulation:
classPadSprite(pygame.sprite.Sprite):
normal=pygame.image.load('pad_normal.png')
hit=pygame.image.load('pad_hit.png')
def__init__(self,position):
self.rect=pygame.Rect(self.normal.get_rect())
self.rect.center=position
defupdate(self,hit_list):
ifselfinhit_list:self.image=self.hit
else:self.image=self.normal
pads=[
PadSprite((200,200)),
PadSprite((800,200)),
PadSprite((200,600)),
PadSprite((800,600)),
]
pad_group=pygame.sprite.RenderPlain(*pads)

nowattheanimationpoint,justbeforewedrawthecar,wechecktoseewhetherthecarspriteiscolliding
withanyofthepads,andpassthatinformationtopad.update()soeachpadknowswhethertodrawitself
hitornot:
collisions=pygame.sprite.spritecollide(car_group,pad_group)
pad_group.update(collisions)
pad_group.draw(screen)

Sonowwehaveacar,runningaroundonthescreen,controlledbytheplayerandwecandetectwhenthecar
hitsotherthingsonthescreen.

Addingobjectives
It'dbeniceifwecoulddeterminewhetherthecarhasmadea"lap"ofthe"circuit"we'veconstructed.We'll
keepinformationindicatingwhichorderthepadsmustbevisited:
classPadSprite(pygame.sprite.Sprite):
normal=pygame.image.load('pad_normal.png')
hit=pygame.image.load('pad_hit.png')
def__init__(self,number,position):
pygame.sprite.Sprite.__init__(self)
self.number=number
self.rect=pygame.Rect(self.normal.get_rect())
self.rect.center=position
self.image=self.normal
pads=[
PadSprite(1,(200,200)),
PadSprite(2,(800,200)),
PadSprite(3,(200,600)),
PadSprite(4,(800,600)),
]
current_pad_number=0

Nowwereplacethepadcollisionfromabovewithcodethatmakessurewehittheminthecorrectorder:
pads=pygame.sprite.spritecollide(car,pad_group,False)
ifpads:
pad=pads[0]
ifpad.number==current_pad_number+1:
pad.image=pad.hit
current_pad_number+=1
elifcurrent_pad_number==4:

88OpenSourceDevelopers'Conference2005

RapidGameDevelopmentInPython
forpadinpad_group.sprites():pad.image=pad.normal
current_pad_number=0

Thelastpartofthattext,resettingthecurrent_pad_numberiswherewe'dflagthattheplayerhasruna
lap.

Addingabackground
Currentlywe'reclearingthescreenoneveryframebeforerendering (screen.fill((0,0,0))).Thisis
quiteslow(thoughyoumightnotnotice)andiseasilyimprovedupon.Firstlyoutsidetheanimationloopwe
loadupabackgroundimageanddrawittothescreen:
background=pygame.image.load('track.png')
screen.blit(self.background,(0,0))

Nowinsidetheloop,butbeforeweupdate(move)thecar,weaskthecar'sspritetoclearitselffromthe
screen.Wedothiswiththepadstoo:
pad_group.clear(screen,background)
car_group.clear(screen,background)

Nowwe'reonlyeverupdatingthesmallareasofscreenthatweneedtoupdate.Afurtheroptimisationwould
betorecognisethatthepadsonlygetupdatedveryinfrequently,andnotdraw/clearthemeachframeunless
theirstateactuallychanges.Thisoptimisationisnotnecessaryjustyet,andagoodruleofthumbistonot
optimiseunlessyoureallyneedtoitjustunnecessarilycomplicatesyourcode.

Phil'sPyGameUtilities(PGU)
pguisanextensionforPyGamewhichincludesseveraltoolsandlibraries.
tools
thetoolsareatileeditorandaleveleditor(tile,isometric,hexagonal).

gui

fullfeaturedgui,htmlrendering,documentlayout,andtextrendering.
gamelibs
thelibrariesincludeaspriteandtileengine(tile,isometric,hexagonal),astateengine,atimer,anda
highscoresystem.

Soya3d
Soya3Disahighlevel3DengineforPython;itaimsatbeingto3DwhatPythonistoprogramming:easy
andpowerful.Itisdesignedwithgamesinmind,focusingbothonperformanceandeaseofuse.Itrelieson
OpenGL,SDLandCal3D.
Soya3DisavailableundertheGPLandcurrentlyrunsonGNU/Linux,thoughportstootherOSareplanned
(MacOSX,Windows,...)sinceitusesonlyportablelibraries.
Featuresincludes:

Objectmodel,includingcamera,light,world,volume,...
Particlesystems
Fullscreen
Tutorialsanddemosincluded
Trees
OpenSourceDevelopers'Conference200589

RapidGameDevelopmentInPython

Raypicking
Landscapes
3Dcharacteranimation(withCal3D)
ExportscriptsforBlender,Obj/Mtl,Mayaand3DSmax
Eventmanagement(keyboard,mouse,...)
Cellshading
Shadows
Environmentmapping

PyWeekOne
ThefirstPythonGameProgrammingChallenge(PyWeek)washeldoverthelastweekinAugust/firstweek
ofSeptember,2005.ThePyWeekchallengegoalswere:
1. Mustbechallengingandfun,
2. Entriesmustbedevelopedduringthechallenge,andmustincorporatesomethemedecidedatthestart
ofthechallenge,
3. Willhopefullyincreasethepublicbodyofpythongametools,codeandexpertise,
4. Willletalotofpeopleactuallyfinishagame,and
5. Mayinspirenewprojects(withreadymadeteams!)
Whathappened:

170individualssignedup
100individualsactuallycompeted(thatdropoffisnormalandexpected)
26finalentriesweresubmitted!
Itwasquiteachallengeandawholelotoffun

Theadditionalgoalsofrunoffbenefits(points3and5)havecometofruitionaswell,withanumberofthe
projectscontinuingtobedevelopedafterthechallenge,andanumberoftheframeworksbeingimprovedasa
resultofthechallenge.
The feedback from the competitors was almost universally positive (the only gripes from people who
unfortunatelygotdraggedawayfromthechallengepartwaythrough).
ThenextchallengewillberunsomewherearoundFebruarytoApril2006.

References
Pythonhttp://www.python.org
PyGamehttp://www.pygame.org
PyOpenGLhttp://pyopengl.sf.net
Soya3dhttp://home.gna.org/oomadness
PGUhttp://www.imitationpickles.org/pgu
PyWeekhttp://www.mechanicalcat.net/tech/PyWeek
GameProgrammingInPythonBookbySeanRileyavailablepublishedbyCharlesRiverMedia

90OpenSourceDevelopers'Conference2005

You might also like