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 gui thetoolsareatileeditorandaleveleditor(tile,isometric,hexagonal). 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