Top Banner
351

Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Apr 07, 2020

Download

Documents

dariahiddleston
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 2: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 3: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PythonGameProgrammingByExample

Page 4: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TableofContents

PythonGameProgrammingByExample

Credits

AbouttheAuthors

AbouttheReviewers

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmore

Whysubscribe?

FreeaccessforPacktaccountholders

Preface

Whatthisbookcovers

Whatyouneedforthisbook

Whothisbookisfor

Conventions

Readerfeedback

Customersupport

Downloadingtheexamplecode

Downloadingthecolorimagesofthisbook

Errata

Piracy

Questions

1.Hello,Pong!

InstallingPython

AnoverviewofBreakout

ThebasicGUIlayout

DivingintotheCanvaswidget

Basicgameobjects

TheBallclass

ThePaddleclass

TheBrickclass

Page 5: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AddingtheBreakoutitems

Movementandcollisions

Startingthegame

PlayingBreakout

Summary

2.CocosInvaders

Installingcocos2d

Gettingstartedwithcocos2d

Handlinguserinput

Updatingthescene

Processingcollisions

Creatinggameassets

SpaceInvadersdesign

ThePlayerCannonandGameLayerclasses

Invaders!

Shoot’emup!

AddinganHUD

Extrafeature–themysteryship

Summary

3.BuildingaTowerDefenseGame

Thetowerdefensegameplay

Cocos2dactions

Intervalactions

Instantactions

Combiningactions

Customactions

Addingamainmenu

Tilemaps

TiledMapEditor

Loadingtiles

Thescenariodefinition

Page 6: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Thescenarioclass

Transitionsbetweenscenes

Gameovercutscene

Thetowerdefenseactors

Turretsandslots

Enemies

Bunker

Gamescene

TheHUDclass

Assemblingthescene

Summary

4.SteeringBehaviors

NumPyinstallation

TheParticleSystemclass

Aquickdemonstration

Implementingsteeringbehaviors

Seekandflee

Arrival

Pursuitandevade

Wander

Obstacleavoidance

Gravitationgame

Basicgameobjects

Planetsandpickups

Playerandenemies

Explosions

Thegamelayer

Summary

5.Pygameand3D

Installingpackages

GettingstartedwithOpenGL

Page 7: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Initializingthewindow

Drawingshapes

Runningthedemo

RefactoringourOpenGLprogram

Processingtheuserinput

AddingthePygamelibrary

Pygame101

Pygameintegration

DrawingwithOpenGL

TheCubeclass

Enablingfaceculling

Basiccollisiondetectiongame

Summary

6.PyPlatformer

Anintroductiontogamedesign

Leveldesign

Platformerskills

Component-basedgameengines

IntroducingPymunk

Buildingagameframework

Addingphysics

Renderablecomponents

TheCameracomponent

TheInputManagermodule

TheGameclass

DevelopingPyPlatformer

Creatingtheplatforms

Addingpickups

Shooting!

ThePlayerclassanditscomponents

ThePyPlatformerclass

Page 8: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Summary

7.AugmentingaBoardGamewithComputerVision

PlanningtheCheckersapplication

SettingupOpenCVandotherdependencies

Windows

Mac

Debiananditsderivatives,includingRaspbian,Ubuntu,andLinuxMint

Fedoraanditsderivatives,includingRHELandCentOS

OpenSUSEanditsderivatives

SupportingmultipleversionsofOpenCV

Configuringcameras

Workingwithcolors

Buildingtheanalyzer

Providingaccesstotheimagesandclassificationresults

Providingaccesstoparametersfortheusertoconfigure

Initializingtheentiremodelofthegame

Updatingtheentiremodelofthegame

Capturingandconvertinganimage

Detectingtheboard’scornersandtrackingtheirmotion

Creatingandanalyzingthebird’s-eyeviewoftheboard

Analyzingthedominantcolorsinasquare

Classifyingthecontentsofasquare

Drawingtext

ConvertingOpenCVimagesforwxPython

BuildingtheGUIapplication

Creatingawindowandbindingevents

CreatingandlayingoutimagesintheGUI

Creatingandlayingoutcontrols

Nestinglayoutsandsettingtherootlayout

Startingabackgroundthread

Closingawindowandstoppingabackgroundthread

Page 9: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Configuringtheanalyzerbasedonuserinput

Updatingandshowingimages

Runningtheapplication

Troubleshootingtheprojectinreal-worldconditions

FurtherreadingonOpenCV

Summary

Index

Page 10: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 11: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PythonGameProgrammingByExample

Page 12: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 13: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PythonGameProgrammingByExampleCopyright©2015PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthors,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:September2015

Productionreference:1230915

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78528-153-2

www.packtpub.com

Page 14: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 15: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CreditsAuthors

AlejandroRodasdePaz

JosephHowse

Reviewers

BenjaminJohnson

DennisO’Brien

AcquisitionEditors

OwenRoberts

SonaliVernekar

ContentDevelopmentEditor

DharmeshParmar

TechnicalEditor

RyanKochery

CopyEditor

VikrantPhadke

ProjectCoordinator

HarshalVed

Proofreader

SafisEditing

Indexer

RekhaNair

Graphics

JasonMonteiro

ProductionCoordinator

ManuJoseph

CoverWork

ManuJoseph

Page 16: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 17: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AbouttheAuthorsAlejandroRodasdePazisacomputerengineerandgamedeveloperfromSeville,Spain.

HecameacrossPythonbackin2009,whilehewasstudyingattheUniversityofSeville.AlejandrodevelopedseveralacademicprojectswithPython,fromwebcrawlerstoartificialintelligencealgorithms.Inhissparetime,hestartedbuildinghisowngamesinPython.HedidaminoringamedesignatHogeschoolvanAmsterdam,wherehecreatedasmall3Dgameenginebasedontheideashelearnedduringthisminor.

Hehasalsodevelopedsomeopensourceprojects,suchasaPythonAPIforthePhilipsHuepersonallightingsystem.YoucanfindtheseprojectsinhisGitHubaccountathttps://github.com/aleroddepaz.

Priortothispublication,AlejandrocollaboratedwithPacktPublishingasatechnicalrevieweronthebookTkinterGUIApplicationDevelopmentHotshot.

Iwouldliketothankmyparents,FelicianoandMaríaTeresa,fortheirabsolutetrustandsupport.Theyhavebeenaninspirationtomeandanexampleofhardwork.

Iwouldalsoliketothankmygirlfriend,Lucía,forherloveandforputtingupwithmewhileIworkedonthisbook.

JosephHowseisawriter,softwaredeveloper,andbusinessownerfromHalifax,NovaScotia,Canada.Computergamesandcodeareimbibedinhisearliestmemories,ashelearnedtoreadandtypebyplayingtextadventureswithhisolderbrother,Sam,andwatchinghimwritegraphicsdemosinBASIC.

Joseph’sotherbooksincludeOpenCVforSecretAgents,OpenCVBlueprints,AndroidApplicationProgrammingwithOpenCV3,andLearningOpenCV3ComputerVisionwithPython.Heworkswithhiscatstomakecomputervisionsystemsforhumans,felines,andotherusers.Visithttp://nummist.comtoreadaboutsomeofhislatestprojectsdoneatNummistMediaCorporationLimited.

IdedicatemyworktoSam,Jan,Bob,Bunny,andmycats,whohavebeenmylifelongguidesandcompanions.

Icongratulatemycoauthorforproducinganexcellentcompendiumofclassicexamplesofgamedevelopment.Iamgratefulfortheopportunitytoaddmychapteroncheckers(draughts)andcomputervision.

Iamalsoindebtedtothemanyeditorsandtechnicalreviewerswhohavecontributedtoplanning,polishing,andmarketingthisbook.IhavecometoexpectanoutstandingteamwhenworkingwithPacktPublishing,andonceagain,allofthemhaveguidedmewiththeirexperienceandsavedmefromsundryerrorsandomissions.Pleasemeetthetechnicalreviewersbyreadingtheirbiographieshere.

Finally,Iwanttothankmyreadersandeverybodyintheopensourcecommunity.Weareunitedinoureffortstobuildandshareallkindsofprojectsandknowledge,pavingthewayforbookssuchasthistosucceed.

Page 18: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 19: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AbouttheReviewersBenjaminJohnsonisanexperiencedPythonprogrammerwithapassionforgameprogramming,softwaredevelopment,andwebdesign.HeiscurrentlystudyingcomputerscienceatTheUniversityofTexasatAustinandplanstospecializeinsoftwareengineering.HismostpopularPythonprojectsincludeanadventuregameengineandaparticlesimulator,bothdevelopedusingPygame.YoucancheckoutBenjamin’slatestPygameprojectsandarticlesonhiswebsiteatwww.learnpygame.com.

IwouldliketothankPacktPublishingforgivingmetheopportunitytoreadandreviewthisexcellentbook!

DennisO’BrienisthedirectorofdatascienceatGameShowNetworkGames.HestudiedphysicsattheUniversityofChicagoasanundergraduateandcompletedhisgraduatestudiesincomputersciencefromtheUniversityofIllinois,Chicago.HewastheprincipalsoftwareengineeratElectronicArts,aseniorsoftwareengineeratLeapfrogEnterprises,andaleadgamedeveloperatJellyvisionGames.

Page 20: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 21: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

www.PacktPub.com

Page 22: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<[email protected]>formoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www2.packtpub.com/books/subscription/packtlib

DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.

Page 23: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

Page 24: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

Page 25: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 26: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PrefaceWelcometoPythonGameProgrammingByExample.Ashobbyistprogrammersorprofessionaldevelopers,wemaybuildawidevarietyofapplications,fromlargeenterprisesystemstowebapplicationsmadewithstate-of-the-artframeworks.However,gamedevelopmenthasalwaysbeenanappealingtopic,maybesimplyforcreatingcasualgamesandnotjustforhigh-budgetAAAtitles.

IfyouwanttoexplorethedifferentwaysofdevelopinggamesinPython,alanguagewithclearandsimplesyntax,thenthisisthebookforyou.Ineachchapter,wewillbuildanewgamefromscratch,usingseveralpopularlibrariesandutilities.Bytheendofthisbook,youwillbeabletoquicklycreateyourown2Dand3Dgames,andhaveahandfulofPythonlibrariesinyourtoolbelttochoosefrom.

Page 27: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

WhatthisbookcoversChapter1,Hello,Pong!,detailstherequiredsoftware,itsinstallation,andthebasicsyntaxofPython:datastructures,controlflowstatements,objectorientation,andsoon.Italsoincludesthefirstgameofthebook,theclassic“Hello,world”game.

Chapter2,CocosInvaders,introducesthecocos2dgameengineandexplainshowtobuildagamesimilartoSpaceInvaderstoputthisknowledgeintopractice.Here,youlearnthebasicsofcollisions,inputhandling,andscenesetup.

Chapter3,BuildingaTowerDefenseGame,iswhereyoulearntodevelopafull-fledgedgamewithcocos2d.Thisgameincludessomeinterestingcomponents,suchasaHUDandamainmenu.

Chapter4,SteeringBehaviors,coversseeminglyintelligentmovementsforautonomouscharacters.Youwillbeaddingthesestrategiesgradually,indifferentlevelsofabasicgamebuiltwithparticlesystems.

Chapter5,Pygameand3D,presentsthefoundationsof3DandguidesyouthroughthebasicstructureofanOpenGLprogram.

Chapter6,PyPlatformer,iswhereyoudevelopa3Dplatformergamewithallthetechniqueslearnedinthepreviouschapter.

Chapter7,AugmentingaBoardGamewithComputerVision,introducesthetopicofcomputervision,whichallowssoftwaretolearnabouttherealworldviaacamera.Inthischapter,youbuildasystemtoanalyzeagameofcheckers(draughts)inrealtimeasplayersmovepiecesonaphysicalboard.

Page 28: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 29: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

WhatyouneedforthisbookTheprojectscoveredinthisbookassumethatyouhaveinstalledPython3.4onacomputerwithWindows,MacOSX,orLinux.Wealsoassumethatyouhaveincludedpipduringtheinstallationprocess,sinceitwillbethepackagemanagerusedtoinstalltherequiredthird-partypackages.

Page 30: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 31: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

WhothisbookisforIfyouhaveeverwantedtocreatecasualgamesinPythonandyouwishtoexplorethevariousGUItechnologiesthatthislanguageoffers,thenthisisthebookforyou.ThistitleisintendedforbeginnersinPythonwithlittleornoknowledgeofgamedevelopment,anditcoversstepbystephowtobuildsevendifferentgames,fromthewell-knownSpaceInvaderstoaclassical3Dplatformer.

Page 32: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 33: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.

Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Forinstance,onUbuntu,youneedtoinstallthepython3-tkpackage.”

Ablockofcodeissetasfollows:

new_list=[]

forelemincollection:

ifelemisnotNone:

new_list.append(elem)

Anycommand-lineinputoroutputiswrittenasfollows:

$python–-version

Python3.4.3

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:“MakesurethatyouchecktheTcl/Tkoptiontoincludethelibrary.”

NoteWarningsorimportantnotesappearinaboxlikethis.

TipTipsandtricksappearlikethis.

Page 34: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 35: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.

Tosendusgeneralfeedback,simplye-mail<[email protected]>,andmentionthebook’stitleinthesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

Page 36: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 37: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

Page 38: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Additionally,up-to-dateexamplecodeforChapter7,AugmentingaBoardGamewithComputerVision,ispostedathttp://nummist.com/opencv.

Page 39: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

DownloadingthecolorimagesofthisbookWealsoprovideyouwithaPDFfilethathascolorimagesofthescreenshots/diagramsusedinthisbook.Thecolorimageswillhelpyoubetterunderstandthechangesintheoutput.Youcandownloadthisfilefromhttps://www.packtpub.com/sites/default/files/downloads/B04505_Graphics.pdf.

Page 40: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.

Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.

Additionally,anyerrataforChapter7,AugmentingaBoardGamewithComputerVision,willbepostedathttp://nummist.com/opencv.

Page 41: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

Page 42: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<[email protected]>,andwewilldoourbesttoaddresstheproblem.

Youcanalsocontacttheauthorsdirectly.AlejandraRodasdePaz,authorofChapters1to6,canbereachedat<[email protected]>.JosephHowse,authorofChapter7,canbereachedat<[email protected]>,andanswerstocommonquestionscanbefoundonhiswebsite,http://nummist.com/opencv.

Page 43: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 44: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Chapter1.Hello,Pong!Gamedevelopmentisahighlyevolvingsoftwaredevelopmentprocess,andithasimprovedcontinuouslysincetheappearanceofthefirstvideogamesinthe1950s.Nowadays,thereareawidevarietyofplatformsandengines,andthisprocesshasbeenfacilitatedwiththearrivalofopensourcetools.

Pythonisafreehigh-levelprogramminglanguagewithadesignintendedtowritereadableandconciseprograms.Thankstoitsphilosophy,wecancreateourowngamesfromscratchwithjustafewlinesofcode.ThereareaplentyofgameframeworksforPython,butforourfirstgame,wewillseehowwecandevelopitwithoutanythird-partydependency.

Inthischapter,wewillcoverthefollowingtopics:

InstallationoftherequiredsoftwareAnoverviewofTkinter,aGUIlibraryincludedinthePythonstandardlibraryApplyingobject-orientedprogrammingtoencapsulatethelogicofourgameBasiccollisionandinputdetectionDrawinggameobjectswithoutexternalassetsDevelopingasimplifiedversionofBreakout,apong-basedgame

Page 45: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

InstallingPythonYouwillneedPython3.4withTcl/Tk8.6installedonyourcomputer.ThelatestbranchofthisversionisPython3.4.3,whichcanbedownloadedfromhttps://www.python.org/downloads/.Here,youcanfindtheofficialbinariesforthemostpopularplatforms,suchasWindowsandMacOS.Duringtheinstallationprocess,makesurethatyouchecktheTcl/Tkoptiontoincludethelibrary.

ThecodeexamplesincludedinthebookhavebeentestedagainstWindows8andMac,butcanberunonLinuxwithoutanymodification.NotethatsomedistributionsmayrequireyoutoinstalltheappropriatepackageforPython3.Forinstance,onUbuntu,youneedtoinstallthepython3-tkpackage.

OnceyouhavePythoninstalled,youcanverifytheversionbyopeningCommandPromptoraterminalandexecutingtheselines:

$python--version

Python3.4.3

Afterthischeck,youshouldbeabletostartasimpleGUIprogram:

$python

>>>fromtkinterimportTk

>>>root=Tk()

>>>root.title('Hello,world!')

>>>root.mainloop()

Thesestatementscreateawindow,changeitstitle,andrunindefinitelyuntilthewindowisclosed.Donotclosethenewwindowthatisdisplayedwhenthesecondstatementisexecuted.Otherwise,itwillraiseanerrorbecausetheapplicationhasbeendestroyed.

Wewillusethislibraryinourfirstgame,andthecompletedocumentationofthemodulecanbefoundathttps://docs.python.org/3/library/tkinter.html.

TipTkinterandPython2

TheTkintermodulewasrenamedtotkinterinPython3.IfyouhavePython2installed,simplychangetheimportstatementwithTkinterinuppercase,andtheprogramshouldrunasexpected.

Page 46: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 47: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AnoverviewofBreakoutTheBreakoutgamestartswithapaddleandaballatthebottomofthescreenandsomerowsofbricksatthetop.Theplayermusteliminateallthebricksbyhittingthemwiththeball,whichreboundsagainstthebordersofthescreen,thebricks,andthebottompaddle.AsinPong,theplayercontrolsthehorizontalmovementofthepaddle.

Theplayerstartsthegamewiththreelives,andiftheymisstheball’sreboundanditreachesthebottomborderofthescreen,onelifeislost.Thegameisoverwhenallthebricksaredestroyed,orwhentheplayerlosesalltheirlives.

Thisisascreenshotofthefinalversionofourgame:

Page 48: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 49: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThebasicGUIlayoutWewillstartoutgamebycreatingatop-levelwindowasinthesimpleprogramweranpreviously.However,thistime,wewillusetwonestedwidgets:acontainerframeandthecanvaswherethegameobjectswillbedrawn,asshownhere:

WithTkinter,thiscaneasilybeachievedusingthefollowingcode:

importtkinterastk

lives=3

root=tk.Tk()

frame=tk.Frame(root)

canvas=tk.Canvas(frame,width=600,height=400,bg='#aaaaff')

frame.pack()

canvas.pack()

root.title('Hello,Pong!')

root.mainloop()

TipDownloadingtheexamplecode

Youcandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Throughthetkalias,weaccesstheclassesdefinedinthetkintermodule,suchasTk,Frame,andCanvas.

Noticethefirstargumentofeachconstructorcallwhichindicatesthewidget(thechildcontainer),andtherequiredpack()callsfordisplayingthewidgetsontheirparentcontainer.ThisisnotnecessaryfortheTkinstance,sinceitistherootwindow.

Page 50: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

However,thisapproachisnotexactlyobject-oriented,sinceweuseglobalvariablesanddonotdefineanynewclassestorepresentournewdatastructures.Ifthecodebasegrows,thiscanleadtopoorlyorganizedprojectsandhighlycoupledcode.

Wecanstartencapsulatingthepiecesofourgameinthisway:

importtkinterastk

classGame(tk.Frame):

def__init__(self,master):

super(Game,self).__init__(master)

self.lives=3

self.width=610

self.height=400

self.canvas=tk.Canvas(self,bg='#aaaaff',

width=self.width,

height=self.height)

self.canvas.pack()

self.pack()

if__name__=='__main__':

root=tk.Tk()

root.title('Hello,Pong!')

game=Game(root)

game.mainloop()

Ournewtype,calledGame,inheritsfromtheFrameTkinterclass.TheclassGame(tk.Frame):definitionspecifiesthenameoftheclassandthesuperclassbetweenparentheses.

Ifyouarenewtoobject-orientedprogrammingwithPython,thissyntaxmaynotsoundfamiliar.Inourfirstlookatclasses,themostimportantconceptsarethe__init__methodandtheselfvariable:

The__init__methodisaspecialmethodthatisinvokedwhenanewclassinstanceiscreated.Here,wesettheobjectattributes,suchasthewidth,theheight,andthecanvaswidget.Wealsocalltheparentclassinitializationwiththesuper(Game,self).__init__(master)statement,sotheinitialstateoftheFrameisproperlyinitialized.Theselfvariablereferstotheobject,anditshouldbethefirstargumentofamethodifyouwanttoaccesstheobjectinstance.Itisnotstrictlyalanguagekeyword,butthePythonconventionistocallitselfsothatotherPythonprogrammerswon’tbeconfusedaboutthemeaningofthevariable.

Intheprecedingsnippet,weintroducedtheif__name__=='__main__'condition,whichispresentinmanyPythonscripts.Thissnippetchecksthenameofthecurrentmodulethatisbeingexecuted,andwillpreventstartingthemainloopwherethismodulewasbeingimportedfromanotherscript.Thisblockisplacedattheendofthescript,sinceitrequiresthattheGameclassbedefined.

Tip

Page 51: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

New-andold-styleclasses

YoumayseetheMySuperClass.__init__(self,arguments)syntaxinsomePython2examples,insteadofthesupercall.Thisistheold-stylesyntax,theonlyflavoravailableuptoPython2.1,andismaintainedinPython2forbackwardcompatibility.

Thesuper(MyClass,self).__init__(arguments)isthenew-classstyleintroducedinPython2.2.Itisthepreferredapproach,andwewilluseitthroughoutthisbook.

Seethechapter1_01.pyscript,whichcontainsthiscode.Sincenoexternalassetsareneeded,youcanplaceitinanydirectoryandexecuteitfromthePythoncommandlinebyrunningchapter1_01.py.Themainloopwillrunindefinitelyuntilyouclickontheclosebuttonofthewindow,oryoukilltheprocessfromthecommandline.

Thisisthestartingpointofourgame,solet’sstartdivingintotheCanvaswidgetandseehowwecandrawandanimateitemsinit.

Page 52: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 53: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

DivingintotheCanvaswidgetSofar,wehavethewindowsetupandnowwecanstartdrawingitemsonthecanvas.TheCanvaswidgetistwo-dimensionalandusestheCartesiancoordinatesystem.Theorigin—the(0,0)orderedpair—isplacedinthetop-leftcorner,andtheaxiscanberepresentedasshowninthefollowingscreenshot:

Keepingthislayoutinmind,wecanusetwomethodsoftheCanvaswidgettodrawthepaddle,thebricks,andtheball:

canvas.create_rectangle(x0,y0,x1,y1,**options)

canvas.create_oval(x0,y0,x1,y1,**options)

Eachofthesecallsreturnsaninteger,whichidentifiestheitemhandle.Thisreferencewillbeusedlatertomanipulatethepositionoftheitemanditsoptions.The**optionssyntaxrepresentsakey/valuepairofadditionalargumentsthatcanbepassedtothemethodcall.Inourcase,wewillusethefillandthetagsoption.

Thex0andy0coordinatesindicatethetop-leftcornerofthepreviousscreenshot,andx1andy1areindicatedinthebottom-rightcorner.

Forinstance,wecancallcanvas.create_rectangle(250,300,330,320,fill='blue',tags='paddle')tocreateaplayer’spaddle,where:

Thetop-leftcornerisatthecoordinates(250,300).Thebottom-rightcornerisatthecoordinates(300,320).

Page 54: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Thefill='blue'meansthatthebackgroundcoloroftheitemisblue.Thetags='paddle'meansthattheitemistaggedasapaddle.Thisstringwillbeusefullatertofinditemsinthecanvaswithspecifictags.

WewillinvokeotherCanvasmethodstomanipulatetheitemsandretrievewidgetinformation.ThistablegivesthereferencestotheCanvaswidgetthatwillbeusedinthischapter:

Method Description

canvas.coords(item) Returnsthecoordinatesoftheboundingboxofanitem.

canvas.move(item,x,y) Movesanitembyahorizontalandaverticaloffset.

canvas.delete(item) Deletesanitemfromthecanvas.

canvas.winfo_width() Retrievesthecanvaswidth.

canvas.itemconfig(item,**options) Changestheoptionsofanitem,suchasthefillcolororitstags.

canvas.bind(event,callback)Bindsaninputeventwiththeexecutionofafunction.ThecallbackhandlerreceivesoneparameterofthetypeTkinterevent.

canvas.unbind(event)Unbindstheinputeventsothatthereisnocallbackfunctionexecutedwhentheeventoccurs.

canvas.create_text(*position,

**opts)

Drawstextonthecanvas.Thepositionandtheoptionsargumentsaresimilartotheonespassedincanvas.create_rectangleandcanvas.create_oval.

canvas.find_withtag(tag) Returnstheitemswithaspecifictag.

canvas.find_overlapping(*position)Returnstheitemsthatoverlaporarecompletelyenclosedbyagivenrectangle.

Youcancheckoutacompletereferenceoftheeventsyntaxaswellassomepracticalexamplesathttp://effbot.org/tkinterbook/tkinter-events-and-bindings.htm#events.

Page 55: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 56: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

BasicgameobjectsBeforewestartdrawingallourgameitems,let’sdefineabaseclasswiththefunctionalitythattheywillhaveincommon—storingareferencetothecanvasanditsunderlyingcanvasitem,gettinginformationaboutitsposition,anddeletingtheitemfromthecanvas:

classGameObject(object):

def__init__(self,canvas,item):

self.canvas=canvas

self.item=item

defget_position(self):

returnself.canvas.coords(self.item)

defmove(self,x,y):

self.canvas.move(self.item,x,y)

defdelete(self):

self.canvas.delete(self.item)

AssumingthatwehavecreatedaCanvaswidgetasshowninourpreviouscodesamples,abasicusageofthisclassanditsattributeswouldbelikethis:

item=canvas.create_rectangle(10,10,100,80,fill='green')

game_object=GameObject(canvas,item)#createnewinstance

print(game_object.get_position())

#[10,10,100,80]

game_object.move(20,-10)

print(game_object.get_position())

#[30,0,120,70]

game_object.delete()

Inthisexample,wecreatedagreenrectangleandaGameObjectinstancewiththeresultingitem.Thenweretrievedthepositionoftheitemwithinthecanvas,movedit,andcalculatedthepositionagain.Finally,wedeletedtheunderlyingitem.

ThemethodsthattheGameObjectclassofferswillbereusedinthesubclassesthatwewillseelater,sothisabstractionavoidsunnecessarycodeduplication.Nowthatyouhavelearnedhowtoworkwiththisbasicclass,wecandefineseparatechildclassesfortheball,thepaddle,andthebricks.

Page 57: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TheBallclassTheBallclasswillstoreinformationaboutthespeed,direction,andradiusoftheball.Wewillsimplifytheball’smovement,sincethedirectionvectorwillalwaysbeoneofthefollowing:

[1,1]iftheballismovingtowardsthebottom-rightcorner[-1,-1]iftheballismovingtowardsthetop-leftcorner[1,-1]iftheballismovingtowardsthetop-rightcorner[-1,1]iftheballismovingtowardsthebottom-leftcorner

Arepresentationofthepossibledirectionvectors

Therefore,bychangingthesignofoneofthevectorcomponents,wewillchangetheball’sdirectionby90degrees.Thiswillhappenwhentheballbouncesagainstthecanvasborder,whenithitsabrick,ortheplayer’spaddle:

classBall(GameObject):

def__init__(self,canvas,x,y):

self.radius=10

self.direction=[1,-1]

self.speed=10

item=canvas.create_oval(x-self.radius,y-self.radius,

x+self.radius,y+self.radius,

fill='white')

super(Ball,self).__init__(canvas,item)

Fornow,theobjectinitializationisenoughtounderstandtheattributesthattheclasshas.Wewillcovertheballreboundlogiclater,whentheothergameobjectshavebeendefinedandplacedinthegamecanvas.

Page 58: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThePaddleclassThePaddleclassrepresentstheplayer’spaddleandhastwoattributestostorethewidthandheightofthepaddle.Aset_ballmethodwillbeusedtostoreareferencetotheball,whichcanbemovedwiththeballbeforethegamestarts:

classPaddle(GameObject):

def__init__(self,canvas,x,y):

self.width=80

self.height=10

self.ball=None

item=canvas.create_rectangle(x-self.width/2,

y-self.height/2,

x+self.width/2,

y+self.height/2,

fill='blue')

super(Paddle,self).__init__(canvas,item)

defset_ball(self,ball):

self.ball=ball

defmove(self,offset):

coords=self.get_position()

width=self.canvas.winfo_width()

ifcoords[0]+offset>=0and\

coords[2]+offset<=width:

super(Paddle,self).move(offset,0)

ifself.ballisnotNone:

self.ball.move(offset,0)

Themovemethodisresponsibleforthehorizontalmovementofthepaddle.Stepbystep,thefollowingisthelogicbehindthismethod:

Theself.get_position()calculatesthecurrentcoordinatesofthepaddleTheself.canvas.winfo_width()retrievesthecanvaswidthIfboththeminimumandmaximumx-axiscoordinates,plustheoffsetproducedbythemovement,areinsidetheboundariesofthecanvas,thisiswhathappens:

Thesuper(Paddle,self).move(offset,0)callsthemethodwithsamenameinthePaddleclass’sparentclass,whichmovestheunderlyingcanvasitemIfthepaddlestillhasareferencetotheball(thishappenswhenthegamehasnotbeenstarted),theballismovedaswell

Thismethodwillbeboundtotheinputkeyssothattheplayercanusethemtocontrolthepaddle’smovement.WewillseelaterhowwecanuseTkintertoprocesstheinputkeyevents.Fornow,let’smoveontotheimplementationofthelastoneofourgame’scomponents.

Page 59: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TheBrickclassEachbrickinourgamewillbeaninstanceoftheBrickclass.Thisclasscontainsthelogicthatisexecutedwhenthebricksarehitanddestroyed:

classBrick(GameObject):

COLORS={1:'#999999',2:'#555555',3:'#222222'}

def__init__(self,canvas,x,y,hits):

self.width=75

self.height=20

self.hits=hits

color=Brick.COLORS[hits]

item=canvas.create_rectangle(x-self.width/2,

y-self.height/2,

x+self.width/2,

y+self.height/2,

fill=color,tags='brick')

super(Brick,self).__init__(canvas,item)

defhit(self):

self.hits-=1

ifself.hits==0:

self.delete()

else:

self.canvas.itemconfig(self.item,

fill=Brick.COLORS[self.hits])

Asyoumayhavenoticed,the__init__methodisverysimilartotheoneinthePaddleclass,sinceitdrawsarectangleandstoresthewidthandtheheightoftheshape.Inthiscase,thevalueofthetagsoptionpassedasakeywordargumentis'brick'.Withthistag,wecancheckwhetherthegameisoverwhenthenumberofremainingitemswiththistagiszero.

AnotherdifferencefromthePaddleclassisthehitmethodandtheattributesituses.TheclassvariablecalledCOLORSisadictionary—adatastructurethatcontainskey/valuepairswiththenumberofhitsthatthebrickhasleft,andthecorrespondingcolor.Whenabrickishit,themethodexecutionoccursasfollows:

Thenumberofhitsofthebrickinstanceisdecreasedby1Ifthenumberofhitsremainingis0,self.delete()deletesthebrickfromthecanvasOtherwise,self.canvas.itemconfig()changesthecolorofthebrick

Forinstance,ifwecallthismethodforabrickwithtwohitsleft,wewilldecreasethecounterby1andthenewcolorwillbe#999999,whichisthevalueofBrick.COLORS[1].Ifthesamebrickishitagain,thenumberofremaininghitswillbecomezeroandtheitemwillbedeleted.

Page 60: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 61: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AddingtheBreakoutitemsNowthattheorganizationofouritemsisseparatedintothesetop-levelclasses,wecanextendthe__init__methodofourGameclass:

classGame(tk.Frame):

def__init__(self,master):

super(Game,self).__init__(master)

self.lives=3

self.width=610

self.height=400

self.canvas=tk.Canvas(self,bg='#aaaaff',

width=self.width,

height=self.height)

self.canvas.pack()

self.pack()

self.items={}

self.ball=None

self.paddle=Paddle(self.canvas,self.width/2,326)

self.items[self.paddle.item]=self.paddle

forxinrange(5,self.width-5,75):

self.add_brick(x+37.5,50,2)

self.add_brick(x+37.5,70,1)

self.add_brick(x+37.5,90,1)

self.hud=None

self.setup_game()

self.canvas.focus_set()

self.canvas.bind('<Left>',

lambda_:self.paddle.move(-10))

self.canvas.bind('<Right>',

lambda_:self.paddle.move(10))

defsetup_game(self):

self.add_ball()

self.update_lives_text()

self.text=self.draw_text(300,200,

'PressSpacetostart')

self.canvas.bind('<space>',

lambda_:self.start_game())

Thisinitializationismorecomplexthatwhatwehadatthebeginningofthechapter.Wecandivideitintotwosections:

Gameobjectinstantiation,andtheirinsertionintotheself.itemsdictionary.Thisattributecontainsallthecanvasitemsthatcancollidewiththeball,soweaddonlythebricksandtheplayer’spaddletoit.Thekeysarethereferencestothecanvasitems,andthevaluesarethecorrespondinggameobjects.Wewillusethisattributelaterinthecollisioncheck,whenwewillhavethecollidingitemsandwillneedtofetchthegameobject.Keyinputbinding,viatheCanvaswidget.Thecanvas.focus_set()callsetsthefocusonthecanvas,sotheinputeventsaredirectlyboundtothiswidget.Thenwe

Page 62: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

bindtheleftandrightkeystothepaddle’smove()methodandthespacebartotriggerthegamestart.Thankstothelambdaconstruct,wecandefineanonymousfunctionsaseventhandlers.SincethecallbackargumentofthebindmethodisafunctionthatreceivesaTkintereventasanargument,wedefinealambdathatignoresthefirstparameter—lambda_:<expression>.

Ournewadd_ballandadd_brickmethodsareusedtocreategameobjectsandperformabasicinitialization.Whilethefirstonecreatesanewballontopoftheplayer’spaddle,thesecondoneisashorthandwayofaddingaBrickinstance:

defadd_ball(self):

ifself.ballisnotNone:

self.ball.delete()

paddle_coords=self.paddle.get_position()

x=(paddle_coords[0]+paddle_coords[2])*0.5

self.ball=Ball(self.canvas,x,310)

self.paddle.set_ball(self.ball)

defadd_brick(self,x,y,hits):

brick=Brick(self.canvas,x,y,hits)

self.items[brick.item]=brick

Thedraw_textmethodwillbeusedtodisplaytextmessagesinthecanvas.Theunderlyingitemcreatedwithcanvas.create_text()isreturned,anditcanbeusedtomodifytheinformation:

defdraw_text(self,x,y,text,size='40'):

font=('Helvetica',size)

returnself.canvas.create_text(x,y,text=text,

font=font)

Theupdate_lives_textmethoddisplaysthenumberoflivesleftandchangesitstextifthemessageisalreadydisplayed.Itiscalledwhenthegameisinitialized—thisiswhenthetextisdrawnforthefirsttime—anditisalsoinvokedwhentheplayermissesaballrebound:

defupdate_lives_text(self):

text='Lives:%s'%self.lives

ifself.hudisNone:

self.hud=self.draw_text(50,20,text,15)

else:

self.canvas.itemconfig(self.hud,text=text)

Weleavestart_gameunimplementedfornow,sinceittriggersthegameloop,andthislogicwillbeaddedinthenextsection.SincePythonrequiresacodeblockforeachmethod,weusethepassstatement.Thisdoesnotexecuteanyoperation,anditcanbeusedasaplaceholderwhenastatementisrequiredsyntactically:

defstart_game(self):

pass

Seethechapter1_02.pymodule,ascriptwiththesamplecodewehavesofar.Ifyouexecutethisscript,itwilldisplayaTkinterwindowliketheoneshowninthefollowing

Page 63: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

figure.Atthispoint,wecanmovethepaddlehorizontally,sowearereadytostartthegameandhitsomebricks:

Page 64: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 65: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

MovementandcollisionsNowthatwehaveplacedallofourgameobjects,wecandefinethemethodsthatwillbeexecutedinthegameloop.Thislooprunsindefinitelyuntilthegameends,andeachiterationupdatesthepositionoftheballandchecksthecollisionthatoccurs.

WiththeCanvaswidget,wecancalculatewhattheitemsthatoverlapwiththegivencoordinatesare,sofornow,wewillimplementthemethodsthatareresponsibleformovingtheballandchangingitsdirection.

Let’sstartwiththemovementoftheballandtheconditionsforrecreatingthebouncingeffectwhenitreachesthecanvasborders:

defupdate(self):

coords=self.get_position()

width=self.canvas.winfo_width()

ifcoords[0]<=0orcoords[2]>=width:

self.direction[0]*=-1

ifcoords[1]<=0:

self.direction[1]*=-1

x=self.direction[0]*self.speed

y=self.direction[1]*self.speed

self.move(x,y)

Theupdatemethoddoesthefollowing:

Itgetsthecurrentpositionandthewidthofthecanvas.Itstoresthevaluesinthecoordsandwidthlocalvariables,respectively.Ifthepositioncollideswiththeleftorrightborderofthecanvas,thehorizontalcomponentofthedirectionvectorchangesitssignIfthepositioncollideswiththeupperborderofthecanvas,theverticalcomponentofthedirectionvectorchangesitssignWescalethedirectionvectorbytheball’sspeedTheself.move(x,y)movestheball

Forinstance,iftheballhitstheleftborder,thecoords[0]<=0conditionevaluatestotrue,sothex-axiscomponentofthedirectionchangesitssign,asshowninthisdiagram:

Page 66: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Iftheballhitsthetop-rightcorner,bothcoords[2]>=widthandcoords[1]<=0evaluatetotrue.Thischangesthesignofboththecomponentsofthedirectionvector,likethis:

Thelogicofthecollisionwithabrickisabitmorecomplex,sincethedirectionoftherebounddependsonthesidewherethecollisionoccurs.

Wewillcalculatethex-axiscomponentoftheball’scenterandcheckwhetheritisbetweenthelowermostanduppermostx-axiscoordinatesofthecollidingbrick.Totranslatethisintoaquickimplementation,thefollowingsnippetshowsthepossiblechangesinthedirectionvectoraspertheballandbrickcoordinates:

Page 67: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

coords=self.get_position()

x=(coords[0]+coords[2])*0.5

brick_coords=brick.get_position()

ifx>brick_coords[2]:

self.direction[0]=1

elifx<brick_coords[0]:

self.direction[0]=-1

else:

self.direction[1]*=-1

Forinstance,thiscollisioncausesahorizontalrebound,sincethebrickisbeinghitfromabove,asshownhere:

Ontheotherhand,acollisionfromtheright-handsideofthebrickwouldbeasfollows:

Thisisvalidwhentheballhitsthepaddleorasinglebrick.However,theballcanhittwobricksatthesametime.Inthissituation,wecannotexecutethepreviousstatementsforeachbrick;ifthey-axisdirectionismultipliedby-1twice,thevalueinthenextiteration

Page 68: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ofthegameloopwillbethesame.

Wecouldcheckwhetherthecollisionoccurredfromaboveorbehind,buttheproblemwithmultiplebricksisthattheballmayoverlapthelateralofoneofthebricksand,therefore,changethex-axisdirectionaswell.Thishappensbecauseoftheball’sspeedandtherateatwhichitspositionisupdated.

Wewillsimplifythisbyassumingthatacollisionwithmultiplebricksatthesametimeoccursonlyfromaboveorbelow.Thatmeansthatitchangesthey-axiscomponentofthedirectionwithoutcalculatingthepositionofthecollidingbricks:

iflen(game_objects)>1:

self.direction[1]*=-1

Withthesetwoconditions,wecandefinethecollidemethod.Aswewillseelater,anothermethodwillberesponsiblefordeterminingthelistofcollidingbricks,sothismethodonlyhandlestheoutcomeofacollisionwithoneormorebricks:

defcollide(self,game_objects):

coords=self.get_position()

x=(coords[0]+coords[2])*0.5

iflen(game_objects)>1:

self.direction[1]*=-1

eliflen(game_objects)==1:

game_object=game_objects[0]

coords=game_object.get_position()

ifx>coords[2]:

self.direction[0]=1

elifx<coords[0]:

self.direction[0]=-1

else:

self.direction[1]*=-1

forgame_objectingame_objects:

ifisinstance(game_object,Brick):

game_object.hit()

Notethatthismethodhitseverybrickinstancethatiscollidingwiththeball,sothehitcountersaredecreasedandthebricksareremovediftheyreachzerohits.

Page 69: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 70: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

StartingthegameFinally,wehavebuiltthefunctionalityneededtorunthegameloop—thelogicrequiredtoupdatetheball’spositionaccordingtotherebounds,andrestartthegameiftheplayerlosesonelife.

NowwecanaddthefollowingmethodstoourGameclasstocompletethedevelopmentofourgame:

defstart_game(self):

self.canvas.unbind('<space>')

self.canvas.delete(self.text)

self.paddle.ball=None

self.game_loop()

defgame_loop(self):

self.check_collisions()

num_bricks=len(self.canvas.find_withtag('brick'))

ifnum_bricks==0:

self.ball.speed=None

self.draw_text(300,200,'Youwin!')

elifself.ball.get_position()[3]>=self.height:

self.ball.speed=None

self.lives-=1

ifself.lives<0:

self.draw_text(300,200,'GameOver')

else:

self.after(1000,self.setup_game)

else:

self.ball.update()

self.after(50,self.game_loop)

Thestart_gamemethod,whichweleftunimplementedinaprevioussection,isresponsibleforunbindingtheSpacebarinputkeysothattheplayercannotstartthegametwice,detachingtheballfromthepaddle,andstartingthegameloop.

Stepbystep,thegame_loopmethoddoesthefollowing:

Itcallsself.check_collisions()toprocesstheball’scollisions.Wewillseeitsimplementationinthenextcodesnippet.Ifthenumberofbricksleftiszero,itmeansthattheplayerhaswon,andacongratulationstextisdisplayed.Supposetheballhasreachedthebottomofthecanvas:

Then,theplayerlosesonelife.Ifthenumberoflivesleftiszero,itmeansthattheplayerhaslost,andtheGameOvertextisshown.Otherwise,thegameisreset

Otherwise,thisiswhathappens:

Thepositionoftheballisupdatedaccordingtoitsspeedanddirection,andthegameloopiscalledagain.The.after(delay,callback)methodonaTkinterwidgetsetsatimeouttoinvokeafunctionafteradelayinmilliseconds.Since

Page 71: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

thisstatementwillbeexecutedwhenthegameisnotoveryet,thiscreatestheloopnecessarytoexecutethislogiccontinuously:

defcheck_collisions(self):

ball_coords=self.ball.get_position()

items=self.canvas.find_overlapping(*ball_coords)

objects=[self.items[x]forxinitems\

ifxinself.items]

self.ball.collide(objects)

Thecheck_collisionsmethodlinksthegameloopwiththeballcollisionmethod.SinceBall.collidereceivesalistofgameobjectsandcanvas.find_overlappingreturnsalistofcollidingitemswithagivenposition,weusethedictionaryofitemstotransformeachcanvasitemintoitscorrespondinggameobject.

RememberthattheitemsattributeoftheGameclasscontainsonlythosecanvasitemsthatcancollidewiththeball.Therefore,weneedtopassonlytheitemscontainedinthisdictionary.Oncewehavefilteredthecanvasitemsthatcannotcollidewiththeball,suchasthetextdisplayedinthetop-leftcorner,weretrieveeachgameobjectbyitskey.

Withlistcomprehensions,wecancreatetherequiredlistinonesimplestatement:

objects=[self.items[x]forxinitemsifxinself.items]

Thebasicsyntaxoflistcomprehensionsisthefollowing:

new_list=[expr(elem)forelemincollection]

Thismeansthatthenew_listvariablewillbealistwhoseelementsaretheresultofapplyingtheexprfunctiontoeacheleminthelistcollection.

Wecanfiltertheelementstowhichtheexpressionwillbeappliedbyaddinganifclause:

new_list=[expr(elem)forelemincollectionifelemisnotNone]

Thissyntaxisequivalenttothefollowingloop:

new_list=[]

forelemincollection:

ifelemisnotNone:

new_list.append(elem)

Inourcase,theinitiallististhelistofcollidingitems,theifclausefilterstheitemsthatarenotcontainedinthedictionary,andtheexpressionappliedtoeachelementretrievesthegameobjectassociatedwiththecanvasitem.Thecollidemethodiscalledwiththislistasaparameter,andthelogicforthegameloopiscompleted.

Page 72: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 73: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PlayingBreakoutOpenthechapter1_complete.pyscripttoseethefinalversionofthegame,andrunitbyexecutingchapter1_complete.py,asyoudidwiththepreviouscodesamples.

Whenyoupressthespacebar,thegamestartsandtheplayercontrolsthepaddlewiththerightandleftarrowkeys.Eachtimetheplayermissestheball,thelivescounterwilldecrease,andthegamewillbeoveriftheballreboundismissedagainandtherearenolivesleft:

Inourfirstgame,alltheclasseshavebeendefinedinasinglescript.However,asthenumberoflinesofcodeincreases,itbecomesnecessarytodefineseparatescriptsforeachpart.Inthenextchapters,wewillseehowitispossibletoorganizeourcodebymodules.

Page 74: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 75: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SummaryInthischapter,webuiltoutfirstgamewithvanillaPython.Wecoveredthebasicsofthecontrolflowandtheclasssyntax.WeusedTkinterwidgets,especiallytheCanvaswidgetanditsmethods,toachievethefunctionalityneededtodevelopagamebasedoncollisionsandsimpleinputdetection.

OurBreakoutgamecanbecustomizedaswewant.Feelfreetochangethecolordefaults,thespeedoftheball,orthenumberofrowsofbricks.

However,GUIlibrariesareverylimited,andmorecomplexframeworksarerequiredtoachieveawiderrangeofcapabilities.Inthenextchapter,wewillintroduceCocos2d,agameframeworkthathelpsuswiththedevelopmentofournextgame.

Page 76: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 77: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Chapter2.CocosInvadersInthepreviouschapter,webuiltagamewithaGraphicalUserInterface(GUI)packagecalledTkinter.SinceitispartofthePython’sstandardlibraries,itwaseasytosetuptheprojectandcreatetherequiredwidgetswithafewlinesofcode.However,thiskindofmodulefallsshortofprovidingthecorefunctionalityofgamedevelopmentbeyondthe2Dgraphics.

Theprojectcoveredinthischapterisdevelopedwithcocos2d.ThisframeworkhasforksfordifferentprogramminglanguagesbesidesPython,suchasC++,Objective-C,andJavaScript.

Thegamethatwewilldevelopisavariantofaclassicalgame,SpaceInvaders.Thisarcadewasasuccessofitstime,andithasbecomepartofthecultureofvideogaming.

Inthistwo-dimensionalgame,theplayermustdefeatwavesofdescendingaliensbyshootingthemwithalasercannon,whichcanbemovedhorizontallyandisprotectedbysomedefensebunkers.Theenemyfireattemptstodestroytheplayer’scannon,anditgraduallydamagesthebunkers.

Inourversion,wewillleaveoutthedefensebunkers,andthegamewilllookasfollows:

Page 78: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Withthisproject,youwilllearnthefollowing:

Thefoundationsofcocos2dHowtoworkwithspritesProcessinginputeventsHandlingmovementsandcollisionsComplementingcocos2dwiththePygletAPI

Page 79: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Installingcocos2dInourpreviousgame,wedidnotuseanythird-partypackagesinceTkinterwaspartofthePythonstandardlibrary.Thisisnotthecasewithournewgameframework,whichmustbedownloadedandinstalled.

ThePythonPackageIndex(alsocalledPyPI)isanofficialsoftwarerepositoryofthird-partypackages,andthankstopackagemanagersystems,thisprocessbecomesverystraightforward.Thepipisthepackagingtoolrecommendation,anditisalreadyincludedinthelatestPythoninstallations.

Youcancheckwhetherpipisinstalledbyrunningpip--version.Theoutputindicatestheversionofpipandwhereitislocated.ThisinformationmayvarydependingonyourPythonversionanditsinstallationdirectory:

$pip--version

pip6.0.8fromC:\Python34\lib\site-packages(python3.4)

Sincecocos2disavailableonthePyPI,youcaninstallitbyrunningthefollowingcommand:

$pipinstallcocos2d

Thiscommanddownloadsandinstallsnotonlythecocos2dpackagebutalsoitsdependencies(Pygletandsix).Oncerun,theconsoleoutputshouldprinttheprogressandindicatethattheinstallationwasexecutedsuccessfully.Toverifythatcocos2discorrectlyinstalled,youcanrunthiscommand:

$python-c"importcocos;print(cocos.version)"

0.6.0

Atthetimeofwritingthisbook,thelatestversionofcocos2dis0.6.0,sotheoutputmightbeagreaterversionthanthisone.

TipThePythonpackagingecosystem

Apartfrompip,thereareotherpackagingutilities,suchaseasy_installandconda.Eachtoolhasdifferentfeaturesandlimitations,butinthisbook,wewillstickwithpipduetoitseaseofuseandthefactthatitisincludedinthelatestPythoninstallationsbydefault.

Page 80: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 81: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Gettingstartedwithcocos2dAswithanyotherframework,cocos2discomposedofseveralmodulesandclassesthatimplementdifferentfeatures.InordertodevelopapplicationswiththisAPI,weneedtounderstandthefollowingbasicconcepts:

Scene:Eachofthestagesofyourapplicationisascene.Whileyourgamemayhavemanyscenes,onlyoneisactiveatanygivenpointintime.Thetransitionbetweenscenesdefinesyourgame’sworkflow.Layer:Everysheetcontainedinascene,whoseoverlaycreatesthefinalappearanceofthescene,iscalledalayer.Forinstance,yourgame’smainscenemayhaveabackgroundlayer,anHUDlayerwithplayerinformationandscores,andananimationlayer,whereeventsandcollisionsbetweenspritesarebeingprocessed.Sprite:Thisisa2Dimagethatcanbemanipulatedthoughcocos2dactions,suchasmove,scale,orrotate.Inourgames,spriteswillrepresenttheplayer’scharacter,enemies,andvisualinformation,suchasthenumberoflivesleft.Director:Thisisasharedobjectthatinitializesthemainwindowandcontrolsthecurrentsceneaswellastherestofthescenesthatareonhold.

AlloftheseclassesexceptDirectorinheritfromCocosNode.Thisclassrepresentselementsthatare—orcontain—nodesthatgetdrawn,anditispartofthecoreofthelibrary.

Thefunctionalityofthisclasscoversparent-childrelations,specialplacements,rendering,andscheduledactions.CocosNodesarealsonotifiedwhentheyareaddedasachildofanothernodeandwhentheyareremovedfromtheirparentnode.

Thistablehasasummaryofthemostrelevantmembersofthisclass:

Member Description

add(child,z=0,

name=None)Thisaddsachild.Itraisesanexceptionifthenamealreadyexists.

remove(child)Thisremovesachildgivenitsnameortheobject.Itraisesanexceptionifthechildisnotonthelistofchildren.

kill() Removesitselffromitsparent.

get_children() Returnsalistofthechildrenofthenode,orderedbytheirzindex.

parent Theparentnode.

position Thepositioncoordinatesrelativetotheparentnode.

x Thex-axispositioncoordinate.

y They-axispositioncoordinate.

schedule(callback) Thisschedulesafunctiontobecalledeveryframe.

Page 82: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

on_enter()Thisiscalledwhenaddedasachildwhiletheparentisintheactivescene,orifthescenegoesactive.

on_exit()Thisiscalledwhenremovedfromitsparentwhiletheparentisintheactivescene,orifthescenegoesactive.

Withthesedefinitionsinmind,wecanbuildourfirstcocos2dapplication.Itwillbeasimpleapplicationinwhichtheplayermustpickupfourobjectsplacedaroundthescene.Theplayercharactercanbemovedwiththearrowkeys,andthereareneithermenusnordisplayinformation.

Theapplicationwilllooklikethis:

Ascanbeseen,thefunctionalitywillbequitebasic,sincethepurposeofthisexampleistofamiliarizeyourselfwiththecocos2dmodules.

Remembertosavethescriptinthesamedirectoryasthe'ball.png'image,sinceitisloadedbyyourgame.Thefirstiterationofourappcontainsthefollowingcode:

importcocos

classMainLayer(cocos.layer.Layer):

def__init__(self):

super(MainLayer,self).__init__()

Page 83: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self.player=cocos.sprite.Sprite('ball.png')

self.player.color=(0,0,255)

self.player.position=(320,240)

self.add(self.player)

if__name__=='__main__':

cocos.director.director.init(caption='Hello,Cocos')

layer=MainLayer()

scene=cocos.scene.Scene(layer)

cocos.director.director.run(scene)

Firstofall,weimportthecocosmodule.Wecanimportonlytherequiredmodulesseparately(cocos.sprite,cocos.layer,cocos.scene,andcocos.director),whichhastheadvantageofaquickerinitialization.However,forthisbasicapp,wewillfollowasimplerapproach.

Wehavedefinedacustomlayerbyinheritingfromthecocos.layer.Layerclass.Inthisexample,wecallitsparent__init__methodonly,butaswewillseelater,thereareotherLayersubclassesthatcanbeusedforspecializedfunctionality,suchasColorLayerandMenu.

Thelayercontainsasinglecocos.sprite.Spriteinstancewiththe'ball.png'image,whichwillrepresenttheplayercharacter.ThecolorattributeindicatestheRGBcolorappliedtothespriteimage,representedasatuplewhosevaluesrangefrom0to255.Thepositionattribute,declaredintheCocosNodeclass,setsthesprite’scentercoordinates.Itisalsopossibletopassthesevaluesaskeywordargumentstothe__init__method.

Themaincodeblockperformsthefollowingactions:

InitializesthemainwindowwiththeDirectorinstanceInstantiatesourLayersubclasswiththespriteCreatesanemptyscenethatcontainsthelayerRunstheDirectormainloopwiththescene

Sincedirector.initwrapsthecreationofaPygletwindow,thecompletelistofkeywordparametersthatcanbepassedtothismethodisavailableatthePygletWindowAPIReference.

Forourcocos2dgames,onlyasubsetoftheseparameterswillbeused,andthemostrelevantkeywordparametersarethefollowing:

Parametername Description Defaultvalue

fullscreen Aflagforcreatingthewindowthatfillstheentirescreen False

resizable Indicateswhetherthewindowisresizable False

vsync Syncwiththeverticalretrace True

width Windowwidthinpixels 640

height Windowheightinpixels 480

Page 84: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

caption Windowtitle(string)

visible Indicateswhetherthewindowisvisibleornot True

Oncewehavesetuptheapplication,weextenditsfunctionalitybyhandlinginputevents.

Page 85: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

HandlinguserinputEachLayerisresponsibleforhandlinginputeventsthroughtheis_event_handlerflag,whichissettoFalsebydefault.IfthisflagissettoTrue,theLayerwillreceiveinputeventsandcallthecorrespondinghandlermethods.

TheseareeventlistenersfromtheLayerclass:

on_mouse_motion(x,y,dx,dy):The(x,y)arethephysicalcoordinatesofthemouse,and(dx,dy)isthedistancevectorcoveredbythemousesincethelastcallon_mouse_press(x,y,buttons,modifiers):Justasinon_mouse_motion,(x,y)arethephysicalcoordinatesofthemouse,butthisiscalledwhenamousebuttonispressedon_mouse_drag(x,y,dx,dy,buttons,modifiers):Acombinationofon_mouse_pressandon_mouse_motion,thisiscalledwhenthemousemovesoverwithoneormorebuttonspressedon_key_press(key,modifiers):Thisiscalledwhenakeyispressedon_key_release(key,modifiers):Thisiscalledwhenakeyisreleased

ThebuttonsparameterisabitwiseORofPygletbuttonconstantsthataredefinedinthepyglet.window.mouseandpyglet.window.keymodulesfortheon_mouseandon_keyevents,respectively.ThemodifiersparameterisabitwiseORofthepyglet.window.keyconstants,suchasShift,Option,andAlt.Youcanfindalltheconstantsdefinedinthismoduleathttps://pythonhosted.org/pyglet/api/pyglet.window.key-module.html.

WhenyoudefineoneofthesemethodsinyourLayersubclassandthecorrespondinginputeventoccurs,themethodiscalled.

Inthisiterationofoursamplegame,wewillprintamessagewhenakeyispressedorreleased:

TipNotethatsincePygletisusedinternallyforeventhandling,weneedthesymbol_stringfunctionfromthepyglet.window.keymoduletodecodethenameofthekey.

importcocos

frompyglet.windowimportkey

classMainLayer(cocos.layer.Layer):

is_event_handler=True

def__init__(self):

super(MainLayer,self).__init__()

self.player=cocos.sprite.Sprite('ball.png')

self.player.color=(0,0,255)

self.player.position=(320,240)

self.add(self.player)

defon_key_press(self,k,m):

print('Pressed',key.symbol_string(k))

Page 86: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

defon_key_release(self,k,m):

print('Released',key.symbol_string(k))

TipWehaveomittedtheif__name__=='__main__'block,butremembertoincludeitinyourscriptsinordertoexecutetheapplication.

Whenyoulaunchit,thekeyboardeventsmustbeprintedontheconsole.Ournextstepwillbetoreflecttheseeventsinthegamestatebymodifyingthesprite’sposition.

TipThePygleteventframework

Eventhandlingisimplementedincocos2dwiththePygleteventframework.Thiseventframeworkallowsyoutodefineemitterswithagiveneventname(suchason_key_press)andregisterlistenersovertheseevents.

RememberthatPygletisthemultimedialibraryonwhichcocos2drelies,sowewillseemoreusagesofthisAPIsincemajorcocos2dapplicationsusePygletmodules.

Page 87: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

UpdatingthesceneSofar,ourgameonlydisplaysthesamecontent,asobjectsarebeingmodified.InawaysimilartotheTkintercallbackthatwasloopingduringtheexecutionofourlastgame,weneedtorefreshourcocos2dapplicationperiodically.

ThisisachievedwiththeschedulemethodfromtheCocosNodeclass.Itschedulesafunctionthatiscalledoneveryframe,andthefirstargumentthatthisfunctionreceivesistheelapsedtimeinsecondssincethelastclocktick.

Whileitispossibletopassadditionalargumentstothiscallbackfunction,wewilldefineanewmethodintheMainLayerclasswiththeelapsedtimeparameteronly:

importcocos

fromcollectionsimportdefaultdict

frompyglet.windowimportkey

classMainLayer(cocos.layer.Layer):

is_event_handler=True

def__init__(self):

super(MainLayer,self).__init__()

self.player=cocos.sprite.Sprite('ball.png')

self.player.color=(0,0,255)

self.player.position=(320,240)

self.add(self.player)

self.speed=100.0

self.pressed=defaultdict(int)

self.schedule(self.update)

defon_key_press(self,k,m):

self.pressed[k]=1

defon_key_release(self,k,m):

self.pressed[k]=0

defupdate(self,dt):

x=self.pressed[key.RIGHT]-self.pressed[key.LEFT]

y=self.pressed[key.UP]-self.pressed[key.DOWN]

ifx!=0ory!=0:

pos=self.player.position

new_x=pos[0]+self.speed*x*dt

new_y=pos[1]+self.speed*y*dt

self.player.position=(new_x,new_y)

Wehavemodifiedon_key_pressandon_key_releasesothattheysavethekeystrokeindefaultdict,aspecialdictionaryfromthecollectionsmodule.Thisdatastructurereturnsthecorrespondingvalueifthekeyispresentorthedefaultvalueofthetypeifthekeydoesnotexist.Inourcase,sinceitisdefaultdict(int),thedefaultvaluefortheabsentkeysis0.

Wehavescheduledtheupdatemethod,andforeachframe,itchecksthevaluesofthe

Page 88: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

pressedarrowkeys.Ifthedifferencebetweenthesevaluesisdistincttozero,thespriteismoved.

Page 89: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ProcessingcollisionsIncocos2d,proximityandcollisionsamongactorsarecalculatedwithaninterfaceofthecocos.collision_modelpackagecalledCollisionManager.Thisinterfacecanbeusedtodeterminewhethertwoobjectsareoverlapping,orwhichobjectsarecloserthanacertaindistancefromagivenobject.

Actorsshouldbeaddedfirsttothesetofobjectsthataremanagedbythecollisionmanager,alsocalledknownentities.Thesetofmanagedobjectsisinitiallyempty.

TheCollisionManagerinterfaceisimplementedbytwoclasses:

CollisionManagerBruteForce:Thisisastraightforwardimplementation.Init,allknownentitiesareusedtocalculateproximityandcollisions.Itisintendedfordebuggingpurposes,anditdoesnotscaleifthesetofknownentitiesgrowsinsize.Itdoesnotneedanyargumentsinitsinitialization.CollisionManagerGrid:Thisdividesthespaceintorectangularcellswithagivenwidthandheight.Whencalculatingthespatialrelationsbetweenobjects,itonlyconsiderstheobjectsfromtheknownentitiesthatoverlapthesamecell.Itsperformanceisbettersuitedforalargenumberofknownentities.The__init__methodrequirestheminimumandmaximumcoordinatesofthespace,thecellwidth,andthecellsize.Therecommendedcellsizeisthemaximumobjectwidthandheightmultipliedby1.25.Notethatasthisfactorincreasesitsvalue,moreobjectsoverlapthesamecellandtheperformancestartstodegrade.

ThereferenceoftheCollisionManagermethodsthatwearegoingtouseinthischapteristhefollowing:

Method Description

add(obj) Makesobjaknownobject

clear() Emptiesthesetofknownobjects

iter_colliding(obj) Returnsaniteratorofobjectscollidingwithobj

knows(obj) Thisistrueifobjisaknownobject

AnobjectcanbecollidedwithifithasamembercalledCshapeanditsvalueisaninstanceofcocos.collision_model.Cshape.Therearetwoclassesincocos2dthatinheritfromCshape:

CircleShape:Thisusesacircleasthegeometricspace.Whenaninstanceiscreated,thecenterofthecircleanditsradiusmustbespecified.TwocirclescollideiftheEuclideandistancebetweentheircentersislessthanthesumoftheirradius.AARectShape:Thisusesarectangleasthegeometricspace.Therectangleisaxis-aligned,whichmeansthatitssidesareparalleltothexandyaxes.Thiscanbewellsuitediftheactorsdonotrotate.InsteadoftheEuclideandistance,itusestheManhattandistancetocalculateacollisionbetweentworectangles.

Page 90: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Inourexample,wewillusecircularshapesforouractors.Inordertotestthecollisionmanager,fourspritesareaddedtotheLayer,andtheywillactaspickups—theywillbedestroyedwhentheycollidewiththeplayer’ssprite.

Besides,sinceCshaperequiresacocos.euclid.Vector2forthecentercoordinates,wewillreplacethetuplesfortheVector2instances.

Beforeaddingthecollisionmanager,wedefineanewclasstoreplaceoursprites.Theactorsofourgamesharetheseattributes,sowewillavoidcodeduplication:

importcocos

importcocos.collision_modelascm

importcocos.euclidaseu

fromcollectionsimportdefaultdict

frompyglet.windowimportkey

classActor(cocos.sprite.Sprite):

def__init__(self,x,y,color):

super(Actor,self).__init__('ball.png',color=color)

self.position=pos=eu.Vector2(x,y)

self.cshape=cm.CircleShape(pos,self.width/2)

Nowthatwehavethisbaseclass,wecanaddtheseobjectswiththerequiredcshapemember.ThecollisionmanagerimplementationthatwewilluseinallourapplicationsisCollisionManagerGrid,sincethebrute-forceimplementationisonlyintendedforreferenceanddebugging:

classMainLayer(cocos.layer.Layer):

is_event_handler=True

def__init__(self):

super(MainLayer,self).__init__()

self.player=Actor(320,240,(0,0,255))

self.add(self.player)

forposin[(100,100),(540,380),\

(540,100),(100,380)]:

self.add(Actor(pos[0],pos[1],(255,0,0)))

cell=self.player.width*1.25

self.collman=cm.CollisionManagerGrid(0,640,0,480,

cell,cell)

self.speed=100.0

self.pressed=defaultdict(int)

self.schedule(self.update)

defon_key_press(self,k,m):

self.pressed[k]=1

defon_key_release(self,k,m):

self.pressed[k]=0

Nowtheupdatemethodmustperformthefollowingstepsinordertobeabletodetectcollisionsofmovingobjects:clearthecollisionmanager,addtheactorstothesetof

Page 91: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

knownentities,anditerateovertheobjects’collisions:

defupdate(self,dt):

self.collman.clear()

for_,nodeinself.children:

self.collman.add(node)

forotherinself.collman.iter_colliding(self.player):

self.remove(other)

x=self.pressed[key.RIGHT]-self.pressed[key.LEFT]

y=self.pressed[key.UP]-self.pressed[key.DOWN]

ifx!=0ory!=0:

pos=self.player.position

new_x=pos[0]+self.speed*x*dt

new_y=pos[1]+self.speed*y*dt

self.player.position=(new_x,new_y)

self.player.cshape.center=self.player.position

Notethestatementinwhichthecshapecenteroftheplayerspriteisupdatedwiththenewposition.Withoutthisline,thecollisionmanagerwillnotdetectanycollisionbetweentheplayerandthepickups,sincetheentitycenterwillalwaysbeintheinitialposition.

Inthechapter2_01.pyscript,youcanfindthecompletecode.Forthisapplication,weonlyneededasinglesprite.However,mostgamesuseseveralimagestorepresentdifferentgamecharactersandtheiranimations.

Inthenextsection,wewillseethespritesthatourSpaceInvadersgamewillhave.

Page 92: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 93: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CreatinggameassetsWewillneedafewbasicimagesforourcocos2dsprites.Theywillbethevisualrepresentationsoftheplayer’scannonandthedifferenttypesofaliens.

Thefollowingimageshowshowthesespritescanbedrawnwithanimageeditorprogram,withthehelpofagrid:

Unfortunately,theusageofimagemanipulationtoolsisbeyondthescopeofthisbook.Thereisawidevarietyintheseprograms,andforbasicsprites(suchastheonesshowninthepreviousimage),youcanuseMSPaintonWindowssystems.

Undertheimgfolder,youcanfindthedifferentimagesthatwewilluseinourgame.Feelfreetoeditthemandchangetheircolors.However,donotmodifytheirsizeasitisusedtodeterminethewidthandtheheightoftheSpriteanditsCshapemember.

TipCreatingyourown“pixelart”

Therearemoreadvancedapplicationsthatcanberunonanyplatform.ThisisthecaseofGIMP,anopensourceprogramthatispartoftheGNUproject.ThespritesusedinthischapterhavebeendrawnwithGIMP2,thecurrentversion.

Youcandownloaditforfreefromhttp://www.gimp.org/downloads/.

Page 94: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 95: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SpaceInvadersdesignBeforewritingourgamewithcocos2d,weshouldconsiderwhichactorsweneedtomodelinordertorepresentourgameentities.InourSpaceInvadersversion,wewillusethefollowingclasses:

PlayerCannon:Thisisthecharactercontrolledbytheplayer.Thespacebarisusedtoshoot,andthehorizontalmovementiscontrolledwiththerightandleftarrowkeys.Alien:Eachoneofthedescendingenemies,withdifferentlooksandscoresdependingonthealientype.AlienColumn:Therearecolumnsoffivealiensintowhicheverygroupofaliensisdivided.AlienGroup:Awholegroupofenemies.Itmoveshorizontallyordescendsuniformly.Shoot:Asmallprojectilelaunchedbytheenemiestowardstheplayercharacter.PlayerShoot:AShootsubclass.Itislaunchedbytheplayerratherthanthealiengroup.

Apartfromtheseclasses,weneedacustomLayerforthegamelogic,whichwillbenamedGameLayer,andabaseclassforthelogicthatissharedamongtheactors—similartotheGameObjectclassfromourpreviousgame.

WewillcallitActor,aswedidinourpreviouscocos2dapplication.Infact,itisquitesimilartothatone,exceptthatthisimplementationaddsthedefinitionoftwomethods:updateandcollide.Theywillbecalledwhenourgameloopupdatesthepositionofthenodesandwhenaknownentityhitstheactor,respectively.

Thismoduleisnew,apartfromthecodewesawintheprevioussection,anditwillcontainthefollowingcode:

importcocos.sprite

importcocos.collision_modelascm

importcocos.euclidaseu

classActor(cocos.sprite.Sprite):

def__init__(self,image,x,y):

super(Actor,self).__init__(image)

self.position=eu.Vector2(x,y)

self.cshape=cm.AARectShape(self.position,

self.width*0.5,

self.height*0.5)

defmove(self,offset):

self.position+=offset

self.cshape.center+=offset

defupdate(self,elapsed):

pass

defcollide(self,other):

pass

Page 96: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThePlayerCannon,Alien,Shoot,andPlayerShootclasseswillextendfromthisclass.Therefore,withthislittlepieceofcode,wehavesetuptheinterfaceforcollisiondetectionandthemovementsofallourgameobjects.

Page 97: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThePlayerCannonandGameLayerclassesAswesawearlier,PlayerCannonisoneofourgameactors.Itmustrespondtotheleftandrightkeystrokestocontrolthehorizontalmovementofthesprite,andtheobjectisdestroyedifanenemy’sshothitsit.

Wewillimplementthisbyoverridingbothupdateandcollide:

fromcollectionsimportdefaultdict

frompyglet.windowimportkey

classPlayerCannon(Actor):

KEYS_PRESSED=defaultdict(int)

def__init__(self,x,y):

super(PlayerCannon,self).__init__('img/cannon.png',x,y)

self.speed=eu.Vector2(200,0)

defupdate(self,elapsed):

pressed=PlayerCannon.KEYS_PRESSED

movement=pressed[key.RIGHT]-pressed[key.LEFT]

w=self.width*0.5

ifmovement!=0andw<=self.x<=self.parent.width-w:

self.move(self.speed*movement*elapsed)

defcollide(self,other):

other.kill()

self.kill()

Todeterminewhetherakeyispressedornot,wehavedefineddefaultdict,asinourbasiccocos2dapplication.Inthiscase,itisaclassattributeofPlayerCannon.Thehorizontalmovementislimitedsothatthecharactercannotleavethecollisionmanagergrid.

TheGameLayerclasswillbethemainlayerofourgame,anditwillberesponsiblefordoingthefollowing:

KeepingtrackofthenumberofplayerlivesleftandthecurrentscoreHandlinginputkeyeventsbysettingitsis_event_handlerflagtotrueCreatingthegameactorsandaddingthemaschildnodesRunningthegameloopbyexecutingascheduledfunctionforeachframewherethecollisionsareprocessedandtheobjectpositionsareupdated

Itsinitialimplementationisthefollowing:

importcocos.layer

classGameLayer(cocos.layer.Layer):

is_event_handler=True

defon_key_press(self,k,_):

PlayerCannon.KEYS_PRESSED[k]=1

defon_key_release(self,k,_):

Page 98: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PlayerCannon.KEYS_PRESSED[k]=0

def__init__(self):

super(GameLayer,self).__init__()

w,h=cocos.director.director.get_window_size()

self.width=w

self.height=h

self.lives=3

self.score=0

self.update_score()

self.create_player()

self.create_alien_group(100,300)

cell=1.25*50

self.collman=cm.CollisionManagerGrid(0,w,0,h,

cell,cell)

self.schedule(self.update)

Thecreate_playermethodaddsaPlayerCannoninthecenterofthescreen.Itwillbecalledeachtimetheplayercharacterneedstoberespawned,whileupdate_scoreincrementsthescorebyaddingthepointsforeachalienthatisdestroyed.Thedefaultvalueofthescore=0argumentinthemethodsignaturemeansthatifnoargumentsarepassed,theargument’svaluewillbe0:

defcreate_player(self):

self.player=PlayerCannon(self.width*0.5,50)

self.add(self.player)

defupdate_score(self,score=0):

self.score+=score

Thecreate_alien_groupmethodinitializestherowsofdescendingaliens.Sincetherequiredclasseshavenotbeenimplementedyet,wewillleaveitwiththepassstatementfornow:

defcreate_alien_group(self,x,y):

pass

Theupdatemethodisacallbackthatwillbescheduledtobeexecutedforeachframe:

defupdate(self,dt):

self.collman.clear()

for_,nodeinself.children:

self.collman.add(node)

ifnotself.collman.knows(node):

self.remove(node)

for_,nodeinself.children:

node.update(dt)

Wedidasmalltrickhere.AswesawintheCollisionManagerreference,theknowsmethodcheckswhetheranobjectisinthesetofknownentities—whichshouldbealwaystruesinceallofthelayer’schildrenhavebeenadded.

However,whenanobjectisoutsideofthesurfacecoveredbythecollisionmanager,itdoesnotoverlapwithanycellofthegridanditisconsideredtobe“notknown.”Inthis

Page 99: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

way,objectsthatmoveoutofthisareaareautomaticallyremoved:

defcollide(self,node):

ifnodeisnotNone:

forotherinself.collman.iter_colliding(node):

node.collide(other)

returnTrue

returnFalse

Finally,thecollidemethodencapsulatesthecalltoiter_collidingandcheckswhetherthenodeisareferencetoavalidobject—thePlayerShootinstancecanbeNoneifthereisnocurrentshoot.

Asusual,wewritethemainblockforthemomentwhenourscriptisrunasthemainmodule:

if__name__=='__main__':

cocos.director.director.init(caption='CocosInvaders',

width=800,height=650)

game_layer=GameLayer()

main_scene=cocos.scene.Scene(game_layer)

cocos.director.director.run(main_scene)

Page 100: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 101: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Invaders!Therearethreeclassesusedtorepresentourinvaders:Alien,AlienColumn,andAlienGroup.

Outoftheseclasses,onlyAlieninheritsfromActorbecauseitistheonlyentitythatisdrawnandcollideswithotherobjects.Insteadofastaticimage,thespritewillbeabasicanimationwhereineachimagewillbeshownfor0.5seconds.ThisisachievedbyloadinganImageGridandcreatinganAnimationfromthisspritegrid.Sincetheseclassesbelongtothepyglet.imagemodule,weneedtoimportthemfirst.

Anotherfunctionofouralienswillbenotifyingitscolumnthattheobjecthasbeenremoved.Thankstothis,thecolumnofaliensknowswhatthebottomoneisandstartsshootingfromitsposition.

YoulearnedfromtheCocosNodereferencethattheon_exitmethodiscalledwhenanodeisremoved,soyouwillbeoverridingittoinformitscorrespondingcolumn.Notethatareferencetothecolumnispassedtothe__init__method.WecouldhaveimplementedthesamefunctionalitywithaPygleteventhandlermechanism,butthatwouldrequirepushingthehandlerstoeveryAlieninstance:

frompyglet.imageimportload,ImageGrid,Animation

classAlien(Actor):

defload_animation(imgage):

seq=ImageGrid(load(imgage),2,1)

returnAnimation.from_image_sequence(seq,0.5)

TYPES={

'1':(load_animation('img/alien1.png'),40),

'2':(load_animation('img/alien2.png'),20),

'3':(load_animation('img/alien3.png'),10)

}

deffrom_type(x,y,alien_type,column):

animation,score=Alien.TYPES[alien_type]

returnAlien(animation,x,y,score,column)

def__init__(self,img,x,y,score,column=None):

super(Alien,self).__init__(img,x,y)

self.score=score

self.column=column

defon_exit(self):

super(Alien,self).on_exit()

ifself.column:

self.column.remove(self)

TheTYPESclassattributehelpusloadtheanimationsonlyonce,atthebeginningofthegame.Thescoreisthenumberofpointsearnedwhentheenemyisdestroyed,whilethecolumnattributecontainsareferencetothecolumntowhichthealienbelongs.

Page 102: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TheAlienColumnclassisresponsibleforinstantiatingthecolumnsofalienswithagivenpattern,likethis:

SincewehavedefinedtheAlien.from_typeutilitymethod,wecallitintheproperordertoplaceeachtypeofalien,asseeninthepreviousfigure:

classAlienColumn(object):

def__init__(self,x,y):

alien_types=enumerate(['3','3','2','2','1'])

self.aliens=[Alien.from_type(x,y+i*60,alien,self)

fori,alieninalien_types]

defremove(self,alien):

self.aliens.remove(alien)

defshoot(self):pass

Theshould_turnmethodcheckswhether,giventhecurrentdirection,thecolumnhasreachedthesideofthescreenornot.ItwillreturnFalseiftherearenoaliensleftinthecolumn:

defshould_turn(self,d):

iflen(self.aliens)==0:

returnFalse

alien=self.aliens[0]

x,width=alien.x,alien.parent.width

returnx>=width-50andd==1orx<=50andd==-1

AlltheAlienColumninstancesformtheAlienGroup.Itismoveduniformly,anditdelegatestheshootinglogictoeachcolumn.Thismovementisimplementedwiththespeedanddirectionobjectattributes.

Whilethealiengroupisbeingupdated,itsumstheelapsedtimesbetweenframes.Whenacertainperiodhaselapsed,thewholegroupismoveddownorlaterally.Thedirectionwilldependonwhetheranycolumnhasreachedthelateralsidesornot:

Page 103: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

classAlienGroup(object):

def__init__(self,x,y):

self.columns=[AlienColumn(x+i*60,y)

foriinrange(10)]

self.speed=eu.Vector2(10,0)

self.direction=1

self.elapsed=0.0

self.period=1.0

defupdate(self,elapsed):

self.elapsed+=elapsed

whileself.elapsed>=self.period:

self.elapsed-=self.period

offset=self.direction*self.speed

ifself.side_reached():

self.direction*=-1

offset=eu.Vector2(0,-10)

foralieninself:

alien.move(offset)

defside_reached(self):

returnany(map(lambdac:c.should_turn(self.direction),

self.columns))

def__iter__(self):

forcolumninself.columns:

foralienincolumn.aliens:

yieldalien

Here,wealsodefinea__iter__method,whichisaspecialmethodthatisinvokedwhenyouiterateoveranobject.Inthisway,wecancallforalieninalien_groupintherestofourcode.

Nowthatwehaveimplementedthelogicofourenemiesandthemovementofthewholegroup,wecanadditsinstantiationtotheGameLayerclass:

defcreate_alien_group(self,x,y):

self.alien_group=AlienGroup(x,y)

foralieninself.alien_group:

self.add(alien)

Thiscreatesanewaliengroupandaddsalltheenemiestothelayeraschildnodes.Thechapter2_02.pyscriptcontainsthecodethatwehavewrittensofar,anditsexecutionwilllooklikethis:

Page 104: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 105: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 106: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Shoot’emup!TheShootactor,byitself,isquitebasic;itonlyrequiresaspeedattributeandoverridestheupdatemethodsothattheobjectismovedbythedistancedeterminedbythisspeedandtheelapsedtimebetweenframes:

classShoot(Actor):

def__init__(self,x,y,img='img/shoot.png'):

super(Shoot,self).__init__(img,x,y)

self.speed=eu.Vector2(0,-400)

defupdate(self,elapsed):

self.move(self.speed*elapsed)

ThePlayerShootclassrequiresabitmorelogic,sincetheplayercannotshootuntilthepreviousbeamhashitanenemyorreachedtheendofthescreen.

Aswewanttoavoidglobalvariables,wewilluseaclassattributetoholdthereferencetothecurrentshot.Whentheshotleavesthescene,thisreferencewillbesettoNoneagain.

WewilloverridethecollidemethodfromtheActorclasssothatboththebeamandthealienaredestroyedwhenacollisionoccurs.ThisisdonebycallingthekillmethoddefinedintheSpriteclass.ItinternallyremovestheCocosNodefromitsparent:

classPlayerShoot(Shoot):

INSTANCE=None

def__init__(self,x,y):

super(PlayerShoot,self).__init__(x,y,'img/laser.png')

self.speed*=-1

PlayerShoot.INSTANCE=self

defcollide(self,other):

ifisinstance(other,Alien):

self.parent.update_score(other.score)

other.kill()

self.kill()

defon_exit(self):

super(PlayerShoot,self).on_exit()

PlayerShoot.INSTANCE=None

Withthisclass,wecanaddthisfunctionalitytotheupdatemethodofPlayerCannon:

defupdate(self,elapsed):

pressed=PlayerCannon.KEYS_PRESSED

space_pressed=pressed[key.SPACE]==1

ifPlayerShoot.INSTANCEisNoneandspace_pressed:

self.parent.add(PlayerShoot(self.x,self.y+50))

movement=pressed[key.RIGHT]-pressed[key.LEFT]

ifmovement!=0:

self.move(self.speed*movement*elapsed)

TheshootmethodinAlienColumn—whichweleftunimplemented—canbemodified

Page 107: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

withournewShootclass.

Torandomlyshootanewbeam,wewillcallrandom.random(),whichreturnsarandomfloatbetweenthesemi-openrangeof(0.0,1.0).Wewillsetalowprobability.Thisisbecausethisfunctionwillbecalledseveraltimespersecond.TherandommoduleispartofthePythonstandardlibrary,soyoucanstartplayingaroundwithitbyaddingtheimportrandomstatementatthebeginningofthescript:

defshoot(self):

ifrandom.random()<0.001andlen(self.aliens)>0:

pos=self.aliens[0].position

returnShoot(pos[0],pos[1]-50)

returnNone

NowtheupdatemethodoftheGameLayerclasscancalculatewhichobjectscollidewiththecannonandtheplayer’scurrentshot,aswellasrandomlyshootfromthealiencolumns:

defupdate(self,dt):

self.collman.clear()

for_,nodeinself.children:

self.collman.add(node)

ifnotself.collman.knows(node):

self.remove(node)

self.collide(PlayerShoot.INSTANCE)

ifself.collide(self.player):

self.respawn_player()

forcolumninself.alien_group.columns:

shoot=column.shoot()

ifshootisnotNone:

self.add(shoot)

for_,nodeinself.children:

node.update(dt)

self.alien_group.update(dt)

Here,respawn_player()decrementsthenumberoflives,anditunschedulesupdateiftherearenolivesleft.Thisstopsthemainloopandrepresentsthegameoversituation:

defrespawn_player(self):

self.lives-=1

ifself.lives<0:

self.unschedule(self.update)

else:

self.create_player()

Inthisiteration,thegamewehavebuiltisverysimilartothefinalversion.Theplayablecharactercanbemovedwiththearrowkeysandisabletoshoot.Theenemiesmoveuniformly,andtheloweralienofeachrowcanshootaswell.Thecodeforthisimplementationcanbefoundinthechapter2_03.pyscript.

Now,theonlydetailleftisdisplayingthegameinformation:thecurrentscoreandthenumberoflivesleft.

Page 108: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AddinganHUDOur“heads-updisplay”willbeanewLayerthatwillbedrawnoverourGameLayer:

classHUD(cocos.layer.Layer):

def__init__(self):

super(HUD,self).__init__()

w,h=cocos.director.director.get_window_size()

self.score_text=cocos.text.Label('',font_size=18)

self.score_text.position=(20,h-40)

self.lives_text=cocos.text.Label('',font_size=18)

self.lives_text.position=(w-100,h-40)

self.add(self.score_text)

self.add(self.lives_text)

defupdate_score(self,score):

self.score_text.element.text='Score:%s'%score

defupdate_lives(self,lives):

self.lives_text.element.text='Lives:%s'%lives

defshow_game_over(self):

w,h=cocos.director.director.get_window_size()

game_over=cocos.text.Label('GameOver',font_size=50,

anchor_x='center',

anchor_y='center')

game_over.position=w*0.5,h*0.5

self.add(game_over)

OurGameLayerwillholdareferencetotheHUDlayer,anditsmethodswillbemodifiedsothattheycancalltheHUDdirectly:

def__init__(self,hud):

super(GameLayer,self).__init__()

w,h=cocos.director.director.get_window_size()

self.hud=hud

self.width=w

self.height=h

#...

Thehudattributewillbeusedwhentheplayerneedstoberespawnedandwhenthescoreisupdated:

defcreate_player(self):

self.player=PlayerCannon(self.width*0.5,50)

self.add(self.player)

self.hud.update_lives(self.lives)

defrespawn_player(self):

self.lives-=1

ifself.lives<0:

self.unschedule(self.update)

self.hud.show_game_over()

else:

self.create_player()

Page 109: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

defupdate_score(self,score=0):

self.score+=score

self.hud.update_score(self.score)

Themainblockshouldalsobemodifiedtocreatethescenewiththetwolayers.Here,wepassthezindextoindicatetheorderingofthechildrenlayers:

if__name__=='__main__':

cocos.director.director.init(caption='CocosInvaders',

width=800,height=650)

main_scene=cocos.scene.Scene()

hud_layer=HUD()

main_scene.add(hud_layer,z=1)

game_layer=GameLayer(hud_layer)

main_scene.add(game_layer,z=0)

cocos.director.director.run(main_scene)

Page 110: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Extrafeature–themysteryshipTocompleteourgame,wewilladdafinalingredient:themysteryshipthatrandomlyappearsatthetopofthescreen:

classMysteryShip(Alien):

SCORES=[10,50,100,200]

def__init__(self,x,y):

score=random.choice(MysteryShip.SCORES)

super(MysteryShip,self).__init__('img/alien4.png',x,y,

score)

self.speed=eu.Vector2(150,0)

defupdate(self,elapsed):

self.move(self.speed*elapsed)

Inanapproachsimilartotheshootinglogicofourenemies,wewillrandomlyaddthisalientoourscheduledfunction:

defupdate(self,dt):

self.collman.clear()

#...

self.alien_group.update(dt)

ifrandom.random()<0.001:

self.add(MysteryShip(50,self.height-50))

Youcanfindthecompleteimplementationofthisgameinthechapter2_04.pyscript.Enjoyyourfirstcocos2dgame!

Page 111: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 112: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SummaryInthischapter,weintroducedcocos2danditsmostrelevantmodules.Wedevelopedourfirstapplicationtogetstartedwiththelibrary,andlaterwebuiltasimplifiedversionofSpaceInvaders.

Thisversioncanbeextendedbyaddingdefensebunkersormorerandomvaluestothepossiblescoresofthemysteryship.Besides,ifyouwanttochangethevisualappearanceoftheinvaders,youcaneditthespritesandcreateyourownenemies!

Inthenextchapter,wewilldevelopacompletetowerdefensegame,withtransitionsbetweenscenes,enhanceddisplayinformation,andcomplexmenus.

Page 113: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 114: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Chapter3.BuildingaTowerDefenseGameInthepreviouschapter,youlearnedthefundamentalsofcocos2d,andnowyouareabletodevelopabasicgamewiththislibrary.However,mostgamesusemorethanasinglescene,andtheircomplexityitisnotlimitedtocollisionsandinputdetection.

Withournextgame,wewilldiveintothecocos2dmodulesthatprovideustheadvancedfunctionalitywearelookingfor:menus,transitions,scheduledactions,andanefficientwayofstoringlevelgraphics.

Inthischapter,wewillcoverthesetopics:

Howtomanipulatespriteswithcocos2dactionsUsingtilemapsasbackgroundlayersAnimatedtransitionsbetweengamescenesHowtocreatescenesthatactasmenusandcutscenesBuildingafull-fledgedcocos2dapplicationwithalltheingredientsyouhavelearnedsofar

Page 115: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThetowerdefensegameplayThegenreoftowerdefensechallengestheplayertostopenemycharactersfromreachingacertainpositionbyplacingstrategicallydifferenttowerssothattheycandefeattheenemiesbeforetheyarriveatthatpoint.Thetowersshootautonomouslytowardstheenemiesthatarewithintheirfiringrange.Thegameisoverwhenaconcretenumberofenemiesreachtheendpoint.

Inourversion,thescenariowillbeameanderingroadinthedesert,andwehavetoprotectabunkerthatisplacedattheendofthisroad,asshowninthefollowingscreenshot.Atthefarend,enemytankswillbespawningrandomly,andwemustplaceturretsthatdestroythembeforetheyreachourbunker.Theturretscanbeplacedinspecificslots,andeachturretspendspartofourlimitedresources.

Page 116: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 117: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Cocos2dactionsInourpreviousgame,wemanipulatedourspritesdirectlythroughtheirmembers,especiallythepositionattribute.Thegameloopupdatedeachactorwiththeelapsedtimefromthepreviousframe.

However,ourtowerdefensegamewillbebasedmainlyoncocos2dactions,whichareorderstomodifyobjectattributessuchastheposition,rotation,orscale.Theyareexecutedbycallingthedo()methodoftheCocosNodeclass.Therefore,anysprite,layer,orscenecanbeavalidtargetofanaction.

Theactionsthatwewillcoverinthissectioncanbedividedintotwomaingroups:intervalactionsandinstantactions.

Page 118: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

IntervalactionsTheseactionshaveaduration,andtheirexecutionendsafterthatcertainduration.Forinstance,ifwewanttomoveaspritetoacertainposition,wedonotwantittohappenimmediatelybutlastafixedamountoftime,givingtheimpressionthatitmoveswithadeterminedspeed.ThiscanbeachievedwiththeMoveToaction:

importcocos

importcocos.actionsasac

if__name__=='__main__':

cocos.director.director.init(caption='Actions101')

layer=cocos.layer.Layer()

sprite=cocos.sprite.Sprite('tank.png',position=(200,200))

sprite.do(ac.MoveTo((250,300),3))

layer.add(sprite)

scene=cocos.scene.Scene(layer)

cocos.director.director.run(scene)

Inthissample,wemoveourtanksprite,initiallyplacedatposition(200,200),toposition(250,300).ThesecondargumentofMoveToindicatestheduration,whichis3seconds.Ifwewantedtousearelativeoffsetinsteadoftheabsolutecoordinates,wecouldhaveusedtheMoveByaction.TheequivalentinthisexamplewouldbeMoveBy((50,100),3).

Anothercommonactionisrotatinganode,andforthispurpose,cocos2dofferstheRotateByandRotateToactions,whichtaketheangleindegreesandthedurationinseconds:

#...

sprite=cocos.sprite.Sprite('tank.png',position=(200,200))

sprite.do(ac.RotateBy(180,5))#Rotate180degreesin5sec

LikeMoveToandMoveBy,thedifferencebetweenRotateToandRotateByistheuseofabsoluteandrelativerotations.

TipThemathmodule

ThePythonstandardlibrarycontainsamodulewithcommonlyusedmathematicalfunctions.Sincecocos2dworkswithdegreesforrotationactions,themath.degrees(radians)andmath.radians(degrees)conversionutilitiesincludedinthemathmodulecanbeextremelyuseful.

Seetheentirefunctionalityprovidedbythismoduleathttps://docs.python.org/3.4/library/math.html.

Thecompletereferenceofinstantactionsincludedinthecocos.actions.interval_actionsmoduleisthefollowing:

Page 119: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Intervalaction Description

Lerp Interpolatesbetweenvaluesforaspecifiedattribute

MoveTo Movesthetargettotheposition(x,y)

MoveBy Movesthetargetbyanoffsetof(x,y)

JumpTo Movesthetargettoapositionsimulatingajumpmovement

JumpBy Movesthetargetsimulatingajumpmovement

Bezier MovesthetargetthroughaBézierpath

Blink Blinksthetargetbyhidingandshowingitanumberoftimes

RotateTo Rotatesthetargettoacertainangle

RotateBy Rotatesatargetclockwisebyanumberofdegrees

ScaleTo Scalesthetargettoazoomfactor

ScaleBy Scalesthetargetbyazoomfactor

FadeOut Fadesoutthetargetbymodifyingitsopacityattribute

FadeIn Fadesinthetargetbymodifyingitsopacityattribute

FadeTo Fadesthetargettoaspecificalphavalue

Delay Delaystheactionbyacertainnumberofseconds

RandomDelay Delaystheactionrandomlybetweenaminimumvalueandamaximumvalueofseconds

Page 120: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

InstantactionsWehavejustintroducedintervalactions.Theseareappliedoveradurationoftime.Nowwewillcoverinstantactions,whichareappliedimmediatelytotheCocosNodeinstance.Actually,theyareinternallyimplementedasintervalactionswithzeroduration.OneexampleofaninstantactionisCallFunc,whichinvokesafunctionwhentheactionisexecuted:

importcocos.actionsasac

defupdate_score():

print('Updatingthescore…')

sprite=cocos.sprite.Sprite('tank.png',position=(200,200))

sprite.do(ac.CallFunc(update_score))

Aswewillseelater,thisactionwillbehandywhenwewanttocallaspecificfunctionduringasequenceofactions.

Notethattheupdate_scoreargumentinthelastlineisnotfollowedby().Thisisasubtlebutimportantdifference;itmeansthatwearecreatingCallFuncwithareferencetotheupdate_scorefunction,insteadofactuallyinvokingit.

Thefollowingtablecontainsalltheactionsdefinedinthecocos.actions.instant_actionsmodule:

Instantaction Description

Place Placesthetargetintheposition(x,y)

CallFunc Callsafunction

CallFuncS Callsafunctionwiththetargetasthefirstargument

Hide HidesthetargetbysettingitsvisibilitytoFalse

Show ShowsthetargetbysettingitsvisibilitytoTrue

ToggleVisibility Togglesthevisibilityofthetarget

Page 121: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CombiningactionsBynow,youknowhowtoapplyactionsseparately,butyouneedtocombinetheminordertoachieveamorecomplexbehavior.

TheActionclass,definedinthecocos.actions.base_actionsmodule,isthebaseclassthatInstantActionandIntervalActioninheritfrom.Itimplementsthe__add__specialmethod,whichiscalledinternallywhenweusethe+operator.

Thisoperatorcreatesasequenceofactionsthatareappliedseriallytothetarget:

sprite=cocos.sprite.Sprite('tank.png',position=(200,200))

sprite.do(ac.MoveBy((80,0),3)+ac.Delay(1)+\

ac.CallFunc(sprite.kill))

Thissnippetmovesourtank80pixelstotherightin3seconds.Itstandsfor1second,andfinally,itisremovedbycallingthekill()method.

Apartfrom__add__,the__or__specialmethodisalsooverridden.Itisinvokedwhenthe|operatorisusedandrunstheactionsinparallel:

sprite=cocos.sprite.Sprite('tank.png',position=(200,200))

sprite.do(ac.MoveTo((500,150),3)|ac.RotateBy(90,2))

Thismovesthespritetothe(500,150)positionin3seconds,andduringthefirst2seconds,itrotates90degreesclockwise.

The__add__and__or__specialmethodscanbecombinedtoproduceasequenceofactionsthatrunbothinparallelandsequentially:

sprite=cocos.sprite.Sprite('tank.png',position=(200,200))

sprite.do((ac.MoveTo((500,150),3)|ac.RotateBy(90,2))+\

ac.CallFunc(sprite.kill))

Thisexampleperformscombinedmovementandrotationasthepreviousone,butwhentheparallelactionsaredone,itremovesthesprite.

TipPython’sspecialmethods

Cocos2dimplementsactionsequencesbyemulatingnumericoperators,butthesecouldhavebeenimplementedwithnormalmethods.Inprograming,thiskindofshortcutofferedbyalanguageiscalledsyntacticsugar,anditsuccinctlyexpressesafunctionalitythatcanalsobeimplementedinamoreverbosemanner.

Page 122: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CustomactionsWehaveafulllistofactionsincludedinthelibrary,butwhatifnoneoftheseactionsperformthevisualeffectthatwedesireandwewanttodefineanewaction?

Wecancreateinstantorintervalactionsbyextendingthecorrespondingclass.Forourgame,wewantanactionthatindicatesvisuallythatanenemytankhasbeenhitbyapplyingaredfiltertothetank,anditshouldgraduallyreturntotheoriginalcolor.

Inthiscase,wedefineanIntervalActionsubclasscalledHit,keepinginmindthatthefollowingstepswillbeperformedinternallybycocos2d:

Theinit(*args,**kwargs)iscalled.Oneofthesekeywordargumentsshouldbethedurationoftheaction,soyoucansetitasyourdurationattribute.Donotconfusethiswiththe__init__specialmethod.Acopyoftheinstanceismade;usually,thisshouldnothaveanysideeffect.Thestart()iscalled.Fromhere,theself.targetattributecanbeused.Theupdate(t)iscalledseveraltimes,wheretisthetimeinthe(0,1)range.Then,update(1)iscalled.Thestop()iscalled.

Itisnotnecessarytooverrideallofthesemethods,butonlythosethatarerequiredtoimplementyouraction.Forinstance,weonlyneedtostorethedurationandupdatethecolorofthespritedependingontheelapsedtime,t:

classHit(ac.IntervalAction):

definit(self,duration=0.5):

self.duration=duration

defupdate(self,t):

self.target.color=(255,255*t,255*t)

Whenupdate(0)isinvoked,thesprite’scolorwillbe(255,0,0),whichlooksasifaredfilterisbeingappliedtotheimage.Theredtonewilldecreaseastheelapsedtime,t,risesmonotonically.

Sinceweknowthatupdate(1)willbecalled,thefinalcolorofthetargetwillbe(255,255,255),andthereisnoneedtoresettheinitialcolorofthesprite.

Thefollowingsnippetshowshowthisnewactioncanbeused:

sprite=cocos.sprite.Sprite('tank.png',position=(200,200))

sprite.do(ac.MoveBy((100,0),3)+Hit()+ac.MoveBy((50,0),2))

IntheChapter3_01.pyscript,youcanseethissnippetwithalloftheusualcoderequiredtorunthescene.

Page 123: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 124: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AddingamainmenuTheSpaceInvadersversionthatwedevelopedinthepreviouschapterstartsanewgameassoonastheapplicationisloaded.Inmostgames,aninitialscreenisdisplayedandtheplayercanchoosebetweendifferentoptionsapartfromstartinganewgame,suchaschangingthedefaultcontrolsortakingalookatthehighscores.

Thecocos.menucocos2dmoduleoffersaLayersubclassnamedMenu,whichservesexactlythispurpose.Byextendingit,youcanoverrideits__init__methodandsetthestyleofthetitle,themenuitems,andtheselectedmenuitem.

TheseitemsarerepresentedasalistofMenuIteminstances.Oncethislistisinstantiated,youcancallthecreate_menumethod,whichbuildsthefinalmenuwiththeactionsthatareexecutedwhenamenuitemisselected.

WhilethebasicMenuItemonlydisplaysastaticlabel,thereareseveralMenuItemsubclassesfordistinctinputmodes:

ToggleMenuItem:TogglesaBooleanoptionMultipleMenuItem:SwitchesbetweenmultiplevaluesEntryMenuItem:ThisisthemenuitemforenteringatextinputImageMenuItem:ShowsaselectableimageinsteadofatextlabelColorMenuItem:Thisisthemenuitemforselectingacolor

AlloftheseclassesexceptImageMenuIteminvokeacallbackfunctionwhenitsvaluechangestothisnewvalueasthefirstargument.

Atypicalusagecouldbethefollowing:

importcocos

fromcocos.menuimport*

importpyglet.app

classMainMenu(Menu):

def__init__(self):

super(MainMenu,self).__init__('Samplemenu')

self.font_title['font_name']='TimesNewRoman'

self.font_title['font_size']=60

self.font_title['bold']=True

self.font_item['font_name']='TimesNewRoman'

self.font_item_selected['font_name']=\

'TimesNewRoman'

self.difficulty=['Easy','Normal','Hard']

m1=MenuItem('NewGame',self.start_game)

m2=EntryMenuItem('Playername:',self.set_player_name,

'JohnDoe',max_length=10)

m3=MultipleMenuItem('Difficulty:',self.set_difficulty,

self.difficulty)

m4=ToggleMenuItem('ShowFPS:',self.show_fps,False)

m5=MenuItem('Quit',pyglet.app.exit)

self.create_menu([m1,m2,m3,m4,m5],

shake(),shake_back())

Page 125: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Whenself.create_menuiscalled,wecanpassacocos2dactionthatisappliedwhenamenuitemisactivatedandwhenitisdeactivated.Thecocos.menumoduleincludesafewactions,andweimportedtheshake()andshake_back()actions,whichperformalittleshakemovementatthecurrentmenuitemandreturntotheoriginalstylewhentheoptionisdeselected,respectively.

Therestofthecodehasbeenomittedforbrevity;youcanfindthecompletescriptinChapter3_02.pywiththeclassmethodsthatarecalledwhenamenuitemisactivated.Notethereferencetothepyglet.app.exitfunction,whichfinalizesthecocos2dapplication.

Withjustafewlinesofcode,wehavebuiltamenuwheretheplayercaninputtheirname,setthegamedifficulty,andtoggletheFPSdisplay.Themenuitemscanbeselectedwiththearrowkeysorthemousepointer.

Thisisthetypeofmainmenuwearelookingforforourtowerdefensegame.Sincethisisonlyonelayer,wecanlateraddabackgroundlayerthatimprovesthevisualappearanceofthemenu.

NowthatyouhaveunderstoodthemenuAPI,let’smoveontothedesignofthemainscene.

Page 126: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 127: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TilemapsThetechniqueoftilemapsbecameasuccessfulapproachtostoringlargeamountsofinformationaboutgameworldswithsmall,reusablepiecesofgraphics.In2Dgames,tilemapsarerepresentedbyatwo-dimensionalmatrixthatreferencestoatileobject.Thisobjectcontainstherequireddataabouteachcelloftheterrain.

Theinitialsheetusedbythetilemapcontainsthe“buildingblocks”ofourscenario,anditlookslikewhatisshowninthefollowingscreenshot:

Startingfromthissimpleimage,wecanbuildagridmapinwhicheachcellisoneofthesquaresthesheetisdividedinto.

Page 128: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TiledMapEditorWewilluseTiledMapEditor,ausefultoolformanipulatingtiledmaps.Itcanbedownloadedforfreefromhttp://www.mapeditor.org/,anditcanberunonmostoperatingsystems,includingWindowsandMacOS.

Thissoftwareisalsowell-suitedforleveldesign,sinceyoucaneditandvisualizetheresultingworldinasimpleway.

Oncewehaveinstalledandlaunchedtheprogram,wecanloadtheimagethatwewilluseforourtilemap.Forthisgame,wehaveusedaPNGimagethatisalreadybundledwiththeMapEditorinstallation.

Inthemenubar,gotoFile|New…tocreateanewmap.Whenanewmapiscreated,youwillbepromptedforsomebasicinformation,suchasthemaporientation,mapsize,sizeofthepattern,andspacingbetweenthecells.Inourgame,wewilluseanorthogonalmapof640x480pixelsand32x32pixelsforeachcell,asshowninthefollowingscreenshot:

NowgotoMap|NewTileset…,enterthenamemap0,andloadthetmw_desert_spacing.pngimagefromtheexamplesfolder,asshowninthisscreenshot:

Page 129: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

YoucanusethetilesshownintheTilesetviewtodrawamaplikethisone:

Oncethemapisdrawn,wecansaveitinseveralfileformats,suchasCSV,JSON,orTXT.WewillchoosetheTMXformat,whichisanXMLfilethatcanbeloadedbycocos2d.GotoFile|Saveas…tostorethetiledmapwiththisformat.

Page 130: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

LoadingtilesThankstothecocos.tilesmodule,wecanloadourtilemapsaslayersandmanipulatethemlikeanyotherCocosNodeinstance.IfourTMXfileandthecorrespondingPNGimagearestoredintheassetsfolder,andthenameofthemapwewanttoloadis'map0',thiswouldbethecodenecessarytoloadit:

importcocos

tmx_file=cocos.tiles.load('assets/tower_defense.tmx')

my_map=tmx_file('map0')

my_map.set_view(0,0,my_map.px_width,my_map.px_height)

scene=cocos.scene.Scene(my_map)

Page 131: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 132: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThescenariodefinitionOncewehaveloadedthetilemap,weneedtolinktheresultingimagewiththegameinformation.Ourscenarioclassshouldcontainthefollowing:

ThepositionswheretheturretscanbeplacedThepositionofthebunkerTheinitialpositionforenemyspawningThepaththattheenemiesmustfollowtoreachthebunker

Inthefollowingscreenshot,wecanseethisdataoverlaidontopofourTMXmap:

Therectanglesrepresenttheslotsinwhichtheplayercanplacetheturrets.Thescenariostoresonlythecentersofthesesquares,becausethegamelayerwilltranslatethesepositionsintoclickablesquares.

Thelinesovertheroadrepresentthepaththattheenemytanksmustfollow.ThismovementwillbeimplementedbychainingtheMoveByandRotateByactions.Wewilldefinetwoconstantsforrotationtowardtheleftortheright,andanauxiliaryfunctionthatreturnsaMoveByactionwhosedurationmakestheenemiesmoveuniformly:

importcocos.actionsasac

RIGHT=ac.RotateBy(90,1)

LEFT=ac.RotateBy(-90,1)

defmove(x,y):

dur=abs(x+y)/100.0

returnac.MoveBy((x,y),duration=dur)

Withtheseconstantsandtheauxiliaryfunction,wecaneasilycreatealistofactionsthat

Page 133: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

willbechainedtorepresentthecompletelistofstepsrequiredtofollowthegreenpathshowninthepreviousscreenshot.Forinstance,thiscouldbeahypotheticalusageoftheseutilitiestocomposeachainofactions:

steps=[move(610,0),LEFT,move(0,160),LEFT,move(-415,0),

RIGHT,move(0,160),RIGHT,move(420,0)]

forstepinsteps:

actions+=step

sprite.do(actions)

Thissolutionnotonlyavoidstheneedtowriteallthestepsonebyone,butalsomakesourcodemoreexpressiveandreadable.Nowlet’sencapsulatethislogicinaclassthatwillholdthisdata.

TipDomain-specificlanguages

Thisuseofabstractionstorepresenthigh-levelconceptsiswidelyfoundinprogramming,andgamedevelopmentisnotanexception.Whenthissyntaxreachesawholedomainofspecializedfeatures,theresultinglanguageiscalledaDomain-specificLanguage(DSL).

WhilethissmalldomainonlycoversspritemovementsandcannotbeconsideredaDSLbyitself,advancedgameenginesincludespecificscriptinglanguagesthatarefocusedongamedevelopment.

Page 134: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThescenarioclassLet’sstartthedefinitionofthescenariomodulebycreatinganemptyfilecalledscenario.pyandopeningitwithourfavoritetexteditor.ThismodulewillcontainthedefinitionoftheScenarioclass,whichgroupsthepreviouslydiscussedinformation,suchasthebunker’sposition,thechainofactionsthattheenemieswillfollow,andsoon:

classScenario(object):

def__init__(self,tmx_map,turrets,bunker,enemy_start):

self.tmx_map=tmx_map

self.turret_slots=turrets

self.bunker_position=bunker

self.enemy_start=enemy_start

self._actions=None

defget_background(self):

tmx_map=cocos.tiles.load('assets/tower_defense.tmx')

bg=tmx_map[self.tmx_map]

bg.set_view(0,0,bg.px_width,bg.px_height)

returnbg

Toretrievethesequenceofactions,wewillusethe@propertydecorator.Itallowsustoaccesstoanattributebyinvokingfunctionsthatwillactasgettersandsetters:

@property

defactions(self):

returnself._actions

@actions.setter

defactions(self,actions):

self._actions=ac.RotateBy(90,0.5)

forstepinactions:

self._actions+=step

Thisdecoratorwrapstheaccesstotheinternal_actionsmember.Givenaninstanceofthisclassnamedscenario,theretrievalofthescenario.actionsmemberwouldtriggerthegetterfunction,whileanassignmenttoscenario.actionswouldtriggerthesetterfunction.

Nowwecandefineafunctionthatinstantiatesascenarioandsetitsmembersbasedonthedispositionofour'map0'.Ifwehaddesignedmorelevels,wecouldhaveaddedmorefunctionsthatcreatethesenewscenarios:

defget_scenario():

turret_slots=[(192,352),(320,352),(448,352),

(192,192),(320,192),(448,192),

(96,32),(224,32),(352,32),(480,32)]

bunker_position=(528,430)

enemy_start=(-80,110)

sc=Scenario('map0',turret_slots,

bunker_position,enemy_start)

sc.actions=[move(610,0),LEFT,move(0,160),

LEFT,move(-415,0),RIGHT,

move(0,160),RIGHT,move(420,0)]

Page 135: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

returnsc

Tousethismodulefromanotherone,wewillimportitwiththisstatement:fromscenarioimportget_scenario.Thenextscriptthatwewillwriteisresponsiblefordefiningourgame’smainmenu.

Page 136: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 137: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TransitionsbetweenscenesInaprevioussection,youlearnedhowtocreateamenuwithcocos2d’sbuilt-inclasses.Themenuofourtowerdefensegamewillbeverysimilar,sinceitfollowsthesamesteps.Itwillbeimplementedinaseparatemodule,namedmainmenu:

classMainMenu(cocos.menu.Menu):

def__init__(self):

super(MainMenu,self).__init__('TowerDefense')

self.font_title['font_name']='Oswald'

self.font_item['font_name']='Oswald'

self.font_item_selected['font_name']='Oswald'

self.menu_anchor_y='center'

self.menu_anchor_x='center'

items=list()

items.append(MenuItem('NewGame',self.on_new_game))

items.append(ToggleMenuItem('ShowFPS:',self.show_fps,

director.show_FPS))

items.append(MenuItem('Quit',pyglet.app.exit))

self.create_menu(items,ac.ScaleTo(1.25,duration=0.25),

ac.ScaleTo(1.0,duration=0.25))

Thismenudisplaysthreemenuitems:

AnoptionforstartinganewgameAtoggleitemforshowingtheFPSlabelAquitoption

Tostartanewgame,wewillneedtheinstanceofthemainscene.Itwillbereturnedbycallingthenew_gamefunctionofmainscene.py,whichhasnotbeendevelopedyet.Besides,wewilladdatransitionwiththeFadeTRTransitionclassfromthecocos.scenes.transitionsmodule.

Atransitionisascenethatperformsavisualeffectbeforesettingthecontrolofanewscene.Itreceivesthissceneasitsfirstargumentandsomeoptions,suchasthetransitiondurationinseconds:

fromcocos.scenes.transitionsimportFadeTRTransition

frommainsceneimportnew_game

game_scene=new_game()#Instanceofcocos.scene.Scene

transition=FadeTRTransition(game_scene,duration=2)

Now,toreplacethecurrentscenewiththenewonedecoratedwithatransition,wecalldirector.push.Inthisway,theon_start_menumethodofourMainMenuclassisimplemented:

defon_new_game(self):

director.push(FadeTRTransition(new_game(),duration=2))

Page 138: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Thevisualeffectproducedbythistransitionisthatthecurrentscene’stilesfadefromtheleft-bottomcornertothetop-rightcorner,asshownhere:

Youcanfindthecompletecodeofthismoduleinthemainmenu.pyscript.

Page 139: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

GameovercutsceneTocreateacutscenewhenthegameisover,wewillneedasimplelayerandatextlabel.SinceasceneisaCocosNode,wecanapplyactionstoit.Toholdthisscreenforamoment,itwillperformaDelayactionthatlasts3seconds,andthenitwilltriggeraFadeTransitiontothemainmenu.

Wewillwrapthesestepsinaseparatefunction,whichwillbepartofanothernewmodule,namedgamelayer.py:

defgame_over():

w,h=director.get_window_size()

layer=cocos.layer.Layer()

text=cocos.text.Label('GameOver',position=(w*0.5,h*0.5),

font_name='Oswald',font_size=72,

anchor_x='center',anchor_y='center')

layer.add(text)

scene=cocos.scene.Scene(layer)

new_scene=FadeTransition(mainmenu.new_menu())

func=lambda:director.replace(new_scene)

scene.do(ac.Delay(3)+ac.CallFunc(func))

returnscene

Thisfunctionwillbecalledfromthegamelayerwhenthebunker’shealthpointsdecreasetozero.

Page 140: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 141: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThetowerdefenseactorsWewilldefineseveralclassesintheactors.pymoduletorepresentthegameobjects.

Aswithourpreviousgame,wewillincludeabaseclass,fromwhichtherestoftheactorclasseswillinherit:

importcocos.sprite

importcocos.euclidaseu

importcocos.collision_modelascm

classActor(cocos.sprite.Sprite):

def__init__(self,img,x,y):

super(Actor,self).__init__(img,position=(x,y))

self._cshape=cm.CircleShape(self.position,

self.width*0.5)

@property

defcshape(self):

self._cshape.center=eu.Vector2(self.x,self.y)

returnself._cshape

IntheActorimplementationinthepreviouschapter,whenthespritewasdisplacedbycallingthemove()method,bothcshapeandpositionwereupdatedatthesametime.However,actionssuchasMoveByonlymodifythespriteposition,andtheCShapecenterisnotupdated.

Tosolvethisissue,wewraptheaccesstotheCShapememberthroughthecshapeproperty.Withthisconstruct,whentheactor.cshapevalueisread,theinternal_cshapeisupdatedbysettingitscenterwiththecurrentspriteposition.

ThissolutionispossiblebecauseanobjectonlyneedsacshapememberinordertobeavalidentityforaCollisionManager,andapropertyisexposedinthesamewayasanyotherattributethatisdirectlyaccessed.

Nowthatouractorbaseclasshasbeendefined,wecanstartimplementingtheclassesfortheturretsandtheenemytanks.

Page 142: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TurretsandslotsTurretsaregameobjectsthatactautonomously;thatis,thereisnoneedtobindinputeventswiththeiractions.Thecollisionshapeisnotthespritesizebutthefiringrange.Therefore,acollisionbetweenaturretandatankmeansthattheenemyisinsidethisrangeandcanbeconsideredavalidtarget:

classTurret(Actor):

def__init__(self,x,y):

super(Turret,self).__init__('turret.png',x,y)

self.add(cocos.sprite.Sprite('range.png',opacity=50,

scale=5))

self.cshape.r=125.0

self.target=None

self.period=2.0

self.reload=0.0

self.schedule(self._shoot)

Theshootinglogicisimplementedbyschedulingafunctionthatincrementsthereloadcounter.Whenthesumofelapsedsecondsreachestheperiodvalue,thecounterisdecreasedandtheturretcreatesashootspritewhoseaimisatthecurrenttarget:

def_shoot(self,dt):

ifself.reload<self.period:

self.reload+=dt

elifself.targetisnotNone:

self.reload-=self.period

offset=eu.Vector2(self.target.x-self.x,

self.target.y-self.y)

pos=self.cshape.center+offset.normalized()*20

self.parent.add(Shoot(pos,offset,self.target))

Apartfromsettingthetarget,acollisionwiththecircularshapethatrepresentsthefiringrangealsorotatestheturret,givingtheimpressionthatitisaimingatthetarget.

Tocalculatetheanglebywhichthespritemustrotate,wecalculatethevectorthatrunsfromtheturrettothetarget.Withtheatan2functionfromthemathmodule,wecalculatetheanglebetweentheπand-πradiansformedbythepositivexaxisandthisvector.Finally,wechangethesignoftheangleandconvertitfromradianstodegrees:

defcollide(self,other):

self.target=other

ifself.targetisnotNone:

x,y=other.x-self.x,other.y-self.y

angle=-math.atan2(y,x)

self.rotation=math.degrees(angle)

Ashootisnotanactor,sinceitisnotrequiredtobeabletocollide.Throughasequenceofactions,itwillmovefromtheturrettothetank’spositionandhitthetargetinstance:

classShoot(cocos.sprite.Sprite):

def__init__(self,pos,offset,target):

super(Shoot,self).__init__('shoot.png',position=pos)

self.do(ac.MoveBy(offset,0.1)+

Page 143: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ac.CallFunc(self.kill)+

ac.CallFunc(target.hit))

ApartfromTurretandShoot,wewillneedaclasstorepresenttheslotsinwhichtheturretscanbeplaced.SincewewilltakeadvantageoftheCollisionManagerfunctionalitytodetectwhetheraCShapehasbeenclickedon,thisclassonlyneedsacshapemember:

classTurretSlot(object):

def__init__(self,pos,side):

self.cshape=cm.AARectShape(eu.Vector2(*pos),side*0.5,side*0.5)

InstancesofthisclasswillbeaddedtoadifferentCollisionManagersothattheydonotconflictwiththerestofthegameobjects.

Page 144: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

EnemiesAnenemytankisanactorthatfollowsthescenariopathuntilitreachesthebunkerattheendoftheroad.Itisdestroyediftheturretshotsreduceitshealthpointstozero.

Theplayerincrementstheirscoreeachtimeatankisdestroyed,soapartfromhealth,wewillneedascoreattributetoindicatehowmanypointstheplayerearns:

classEnemy(Actor):

def__init__(self,x,y,actions):

super(Enemy,self).__init__('tank.png',x,y)

self.health=100

self.score=20

self.destroyed=False

self.do(actions)

Sincewemustdifferentiatebetweenwhetherthetankhasexplodedbecauseithasbeendefeatedbytheturretsorbecauseithasreachedtheendpoint,wewilluseadestroyedflag.Thisflagwillbesettotrueonlyifthetankisdestroyedbyaturret.

WealsochecktheCocosNodeflagcalledis_running,sinceitissettofalsewhenthenodeisremoved.Thus,wecanpreventthetankfrombeingremovedwhenithasalreadybeenkilled:

defhit(self):

self.health-=25

self.do(Hit())

ifself.health<=0andself.is_running:

self.destroyed=True

self.explode()

defexplode(self):

self.parent.add(Explosion(self.position))

self.kill()

LiketheanimationsofourSpaceInvadersgame,anexplosionwillbesimulatedwithafastsequenceofspritesthatlastsfor0.07seconds.Toavoidhavingtoomanyspriteinstancesinourlayer,thekill()methodiscalled1secondaftertheobjectinstantiation:

raw=pyglet.image.load('explosion.png')

seq=pyglet.image.ImageGrid(raw,1,8)

explosion_img=Animation.from_image_sequence(seq,0.07,False)

classExplosion(cocos.sprite.Sprite):

def__init__(self,pos):

super(Explosion,self).__init__(explosion_img,pos)

self.do(ac.Delay(1)+ac.CallFunc(self.kill))

Page 145: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

BunkerYoumightrememberthedescriptionofthegameplay.Weneedtokeeptheenemiesawayfromaspecificarea.Inourversion,itisrepresentedbyabunkerthatisplacedattheendoftheroad.

Thebunkerinstanceonlyneedstoprocesstheenemycollisionsanddecreaseitshealthpoints.Theinitialnumberofhealthpointsis100,andeachcollisionsubtracts10pointsfromthistotal:

classBunker(Actor):

def__init__(self,x,y):

super(Bunker,self).__init__('bunker.png',x,y)

self.hp=100

defcollide(self,other):

ifisinstance(other,Enemy):

self.hp-=10

other.explode()

ifself.hp<=0andself.is_running:

self.kill()

AswedidwiththeTankclass,wecheckwhethertheCocosNodeflagis_runningissettotruetoavoidcallingkill()whenthebunkerhasalreadybeenremoved.

Page 146: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 147: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

GamesceneThegamelayercontainsseveralattributesforholdingthereferencetotheHUDlayer,thescenario,andthegameinformation,suchasthescoreorthenumberofpointsthatcanbespenttobuildnewturrets.

Theseclassesareaddedtothegamelayermodule,whichhascontainedonlythegameovertransitionsofar:

classGameLayer(cocos.layer.Layer):

def__init__(self,hud,scenario):

super(GameLayer,self).__init__()

self.hud=hud

self.scenario=scenario

self.score=self._score=0

self.points=self._points=40

self.turrets=[]

w,h=director.get_window_size()

cell_size=32

self.coll_man=cm.CollisionManagerGrid(0,w,0,h,

cell_size,

cell_size)

self.coll_man_slots=cm.CollisionManagerGrid(0,w,0,h,

cell_size,

cell_size)

forslotinscenario.turret_slots:

self.coll_man_slots.add(actors.TurretSlot(slot,

cell_size))

self.bunker=actors.Bunker(*scenario.bunker_position)

self.add(self.bunker)

self.schedule(self.game_loop)

Anotherdifferencefromourpreviouscocos2dgameistheusageoftwocollisionmanagers:coll_manforthegameactorsandcoll_man_slotsfortheturretslots.Whilethefirstoneisupdatedduringeachiterationofthegameloop,thesecondonecontainsstaticshapesthatdonotconflictwiththeactors’collisions.Asaresult,weavoidunnecessaryadditionsandremovalsfromtheturretslotshapes;thus,improvingthecollisioncheckingperformance.

Bothpointsandscorearepropertiesthat,apartfromaccessingthe_pointsand_scoreinternalattributes,updatetheHUDwiththenewnumericvalue.Here,wewillshowthepointsproperty,butscorehasasimilarimplementation:

@property

defpoints(self):

returnself._points

@points.setter

defpoints(self,val):

self._points=val

self.hud.update_points(val)

Page 148: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Thegameloopisquitesimplesincethelogicofthegameismainlyimplementedwithactions:

defgame_loop(self,_):

self.coll_man.clear()

forobjinself.get_children():

ifisinstance(obj,actors.Enemy):

self.coll_man.add(obj)

forturretinself.turrets:

obj=next(self.coll_man.iter_colliding(turret),None)

turret.collide(obj)

forobjinself.coll_man.iter_colliding(self.bunker):

self.bunker.collide(obj)

ifrandom.random()<0.005:

self.create_enemy()

Thesearethestatementsperformedforeachframe:

Itupdatesthecollisionmanagerwiththepositionsoftheenemytanks.Foreachturret,wecheckwhetherthereisanyenemywithinthefiringrange.Ifso,wecallthecollide()methodoftheTurretclass.Wealsocheckwhetheracollisionwiththebunkerhasoccurred.Sincetheonlyentitiesmanagedbyself.coll_manaretheenemytanks,wedonothavetoworryaboutverifyingthattheotherobjectisatankandnotaturret.Enemiesarerandomlyspawnedwithagivenprobability.Weleftastaticvalue,butitcouldhavebeencalculateddependingonthenumberofenemiesdefeatedsothatthegamebecomesincreasinglymoredifficult.

Thecreate_enemy()methodplacesanewtankattheinitialposition.Topreventthemfromalwaysspawningatthesamecoordinates,wewillapplyarandomoffsetof+/-10pixelsinboththexandycomponents:

defcreate_enemy(self):

enemy_start=self.scenario.enemy_start

x=enemy_start[0]+random.uniform(-10,10)

y=enemy_start[1]+random.uniform(-10,10)

self.add(actors.Enemy(x,y,self.scenario.actions))

Thelayerwillprocessuserinputasusual,andwewillregisteronlyonemethodtoprocessmouseevents.Withtheobjs_touching_point()method,wewillknowwhetheranyslothasbeenclickedon.Iftheplayerhasenoughpoints,anewturretinstanceisplacedatthecenteroftheslot’sposition:

is_event_handler=True

defon_mouse_press(self,x,y,buttons,mod):

slots=self.coll_man_slots.objs_touching_point(x,y)

iflen(slots)andself.points>=20:

self.points-=20

slot=next(iter(slots))

turret=actors.Turret(*slot.cshape.center)

Page 149: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self.turrets.append(turret)

self.add(turret)

Finally,weoverridetheremove()methodtodetectwhichtypeofobjecthasbeenremoved:

defremove(self,obj):

ifobjisself.bunker:

director.replace(SplitColsTransition(game_over()))

elifisinstance(obj,actors.Enemy)andobj.destroyed:

self.score+=obj.score

self.points+=5

super(GameLayer,self).remove(obj)

Ifthenodeisthebunker,itmeansthattheplayerhaslostthegame,andthedirectorreplacesthecurrentscenewiththeGameOvercutscene.Ifthenodeisanenemytank,thescoreandthepointsareupdatedbeforeactuallyremovingtheobject.

Page 150: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TheHUDclassThislayerisresponsiblefordisplayingthegameinformation,afunctionalitysimilartotheHUDwebuiltinourpreviousgame:

classHUD(cocos.layer.Layer):

def__init__(self):

super(HUD,self).__init__()

w,h=director.get_window_size()

self.score_text=self._create_text(60,h-40)

self.score_points=self._create_text(w-60,h-40)

def_create_text(self,x,y):

text=cocos.text.Label(font_size=18,font_name='Oswald',

anchor_x='center',anchor_y='center')

text.position=(x,y)

self.add(text)

returntext

defupdate_score(self,score):

self.score_text.element.text='Score:%s'%score

defupdate_points(self,points):

self.score_points.element.text='Points:%s'%points

Page 151: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AssemblingthesceneToconcludeourmainscene.pymodule,wewilldefinethenew_game()function.Thisfunctionisusedbythemainmenuwhenanewgameisstarted.Itreturnsthescenewiththetilemap,theHUDlayer,andthegamelayerinitializedanddisplayedinthecorrectorder:

defnew_game():

scenario=get_scenario()

background=scenario.get_background()

hud=HUD()

game_layer=GameLayer(hud,scenario)

returncocos.scene.Scene(background,game_layer,hud)

Thegamewillbestartedwiththeconditionalblockthatwesawinourpreviousgames.Apartfrominitializingthedirectorandrunningthescene,wewillloadtheOswaldfontandaddtheimagestotheresourcepathwiththePygletAPI:

fromcocos.directorimportdirector

importpyglet.font

importpyglet.resource

frommainmenuimportnew_menu

if__name__=='__main__':

pyglet.resource.path.append('assets')

pyglet.resource.reindex()

pyglet.font.add_file('assets/Oswald-Regular.ttf')

director.init(caption='TowerDefense')

director.run(new_menu())

Thisissavedinthetowerdefense.pyscript.Thefollowingscreenshotshowsthefinalprojectlayout:

Page 152: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 153: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 154: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SummaryWiththisproject,wesawhowtoincludemenus,transitions,andactionsinourcocos2dapplications.Ourcodeissplitintomultiplemodules,enhancingabetterorganizationofourgames.

Thisgamecanbethebaseforamorecomplextowerdefensegame.Youcancustomizethenumberofpointsrequiredtocreateanewturret,orchangetheprobabilityofspawningenemies.Asanexercise,trytocreateanewTMXmapwithMapEditoranddefineacustomscenariobasedonthisbackground.Yourimaginationisthelimit!

Page 155: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 156: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Chapter4.SteeringBehaviorsOurpreviouscocos2dgamewasbasedonactions,somovementsandrotationswerepredefinedandthecharacterswerenotinfluencedbythecurrentstateofthegame.However,arecurringproblemingamedevelopmentishowtorecreatelife-likeanimations,suchaspursuingatargetoravoidingmovingobstacles.

Now,youwilllearnhowtoapplysteeringbehaviors,atechniqueusedtocreateseeminglyintelligentmovementsforautonomouscharacters.Theimplementationofthesestrategiesachievestheabilitytonavigatethroughthegameworldwithimprovisedpatterns.

Finally,wewillputthesestrategiesinpracticewithparticlesystems,aCocos2dmodulethatwehavenotworkedwithsofar.Sincewewillusesimpleshapes,theseparticlesystemswillrepresentourcharacters,withtheadvantageofusnotrequiringexternalassetsforourapplications.

Inthischapter,wewillcoverthesetopics:

BasicconceptsofsteeringbehaviorsBehaviorsforindividualsandgroupsMixingthesestrategiesHowtorenderparticlesystemswithcocos2d

Page 157: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

NumPyinstallationTousetheparticlesystemsupportofcocos2d,itisnecessarytoinstallNumPy,aPythonpackageusedtooperatewithlargearraysandmatricesinanefficientway.SinceitcontainsseveralCmodules,itmightbedifficulttoinstallitonWindowssystemsbecauseyoumightnothavetheappropriatecompiler.

YoucandownloadtheofficialbinariesforWindowsandMacOSXfromtheNumPysiteathttp://sourceforge.net/projects/numpy/files/NumPy/1.9.2/.

AnotheroptionistodownloadtheunofficialcompiledbinariesfromChristophGohlke’swebsiteathttp://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy.Here,thepackagesareuploadedas.whlfiles.Thisistheextensionofthewheelformatandcanbeinstalledwithpip:

$pipinstallnumpy‑1.9.2+mkl‑cp34‑none‑win32.whl

Inbothcases,remembertoinstallthebinariesforPython3.4,sincetheversionsforPython2.7and3.3areavailablefordownloadaswell.

Youcancheckwhethertheinstallationwassuccessfulbyrunningthiscommand:

$python-c"importnumpy;print(numpy.version.version)"

1.9.2

OnceNumPyisinstalled,youhavealltherequirementsforsupportingparticlesystemsincocos2d.

Page 158: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TheParticleSystemclassThebaseclassforcocos2dparticlesystemsisParticleSystem,asdefinedinthecocos.particlemodule.Thefollowingtablelistssomeofthemostrelevantclassmembers.Notethattheonesendingin_varindicatethatrandomvariancecanbeappliedtothebasevalue.

Classmember Description

Active Indicateswhethertheparticlesystemisspawningnewparticlesornot.

DurationThedurationofthesysteminseconds.Thisvalueis-1forinfiniteduration.

Gravity Gravityoftheparticles.

angle,angle_var Theangulardirectionoftheparticlesmeasuredindegrees.

speed,speed_var Thespeedoftheparticles.

tangential_accel,

tangential_accel_valTangentialacceleration.

radial_accel,radial_accel_var Radialacceleration.

size,size_var Thesizeoftheparticles.

life,life_var Thetimeinsecondsforwhicheachparticlewilllive.

start_color,start_color_var Thestartcoloroftheparticles.

end_color,end_color_val Theendcoloroftheparticles.

total_particles Themaximumnumberofparticles.

Youcancreateyourownparticlesystemsbyextendingthisclassandredefiningthevaluesoftheseclassmembers.

Cocos2dincludesanothermodule,cocos.particles_systems,withsomepredefinedParticleSystemsubclasses,eachproducingadifferentvisualeffect.ThenamesoftheseclassesareFireworks,Spiral,Meteor,Sun,Fire,Galaxy,Flower,Explosion,andSmoke.

Page 159: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AquickdemonstrationThiscodeisabasicexamplethatshowshowtoaddastaticParticleSystemtoourlayerwithoutprocessinganyuserinputoractions:

importcocos

importcocos.particle_systemsasps

classMainLayer(cocos.layer.Layer):

def__init__(self):

super(MainLayer,self).__init__()

particles=ps.Spiral()

particles.position=(320,240)

self.add(particles)

if__name__=='__main__':

cocos.director.director.init(caption='Particlesexample')

scene=cocos.scene.Scene(MainLayer())

cocos.director.director.run(scene)

ParticleSysteminheritsfromCocosNode.Therefore,theinstancehastheusualmembers’position,rotation,andscale,aswellasthedo()andkill()methods.

Runningthisscriptshowsthefollowingpredefinedparticlesystem:

Page 160: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Youcanreplaceps.Spiral()withotherbuilt-inparticlesystems,suchasps.Galaxy()orps.Fireworks().ManyIntegratedDevelopmentEnvironments(IDE),suchasPyCharmorPyDevm,offercodecompletionandwilllistallthenamesthataredefinedinamoduleaftertypingitsname.

Page 161: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 162: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ImplementingsteeringbehaviorsThestrategiesthatwewillcoverherehavebeentakenfromCraigReynolds’spaperSteeringBehaviorsforAutonomousCharacters,writtenin1999.Ithasbecomeawell-knownreferenceforimplementingautonomousmotioninaneasymannerfornon-playablecharacters.

Youcancheckouttheonlineversionathttp://www.red3d.com/cwr/steer/gdc99/.Inthissection,youwilllearnhowtoimplementthefollowingkindsofbehavior:

SeekandfleeArrivalPursuitandevadeWanderObstacleavoidance

Page 163: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SeekandfleeTheseekbehaviormovesthecharactertowardsaspecificpositioninthespace.Thisbehaviorisbasedonthecombinationoftwoforces:thecharacter’svelocityandthesteeringforce.Thisforceiscalculatedasthedifferencebetweenthedesiredvelocity(thedirectionfromthecharactertothetarget)andthecharacter’scurrentvelocity.

Notethatthisisnotaforceinthestrictphysicsdefinition;itisjustanothervelocityvector.Youcanthinkofitasacorrectionofthecharacter’svelocity,andtheresultingpathwillbeasmoothcurvethatadjuststhevelocityuntilitisalignedtowardsthetarget.

Fleeistheinverseofseek,anditmakesthecharactermoveawayfromthetarget.Itmeansthatthesteeringforcepushesthecharacterawayfromthetarget.

Youcanseebothseekandfleerepresentedgraphicallyinthefollowingdiagram.Thecurvedpathsshowthefinaldirectiononcetheforcesarecombined,therightonetowardsthetarget(seek),andtheleftoneawayfromthetarget(flee).

Forourimplementation,weneedaclassthatrepresentstheautonomouscharacterwiththefollowingmembers:

velocity:Atwo-dimensionalvectorrepresentingtheactor’svelocityspeed:Theactor’sspeed,measuredinthenumberofpixelsperframemax_force:Themaximummagnitudeofthesteeringforcemax_velocity:Themaximummagnitudeofthevelocityvectortarget:Thepositionthattheactortriestoreach

Besides,toputourparticlesystemsintopractice,wewillextendCocosNodeinsteadof

Page 164: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SpriteandrepresentouractorwiththeSunparticlesystem:

importcocos

importcocos.euclidaseu

importcocos.particle_systemsasps

classActor(cocos.cocosnode.CocosNode):

def__init__(self,x,y):

super(Actor,self).__init__()

self.position=(x,y)

self.velocity=eu.Vector2(0,0)

self.speed=2

self.max_force=5

self.max_velocity=200

self.target=None

self.add(ps.Sun())

self.schedule(self.update)

Theupdate()methodwillcomputethenewpositionofthecharacterforeachframe,basedonthecurrentvelocityandthetargetthatitisseeking:

defupdate(self,dt):

ifself.targetisNone:

return

distance=self.target-eu.Vector2(self.x,self.y)

steering=distance*self.speed-self.velocity

steering=truncate(steering,self.max_force)

self.velocity=truncate(self.velocity+steering,

self.max_velocity)

self.position+=self.velocity*dt

Stepbystep,weperformthefollowingoperations:

Calculatethedistancetothetarget.Wescalethisdistanceagainstthespeedandsubtractthecurrentvelocity.Thisgivesusthesteeringforce.Truncatethisforcewiththemaximumforce.Sumthesteeringforcewiththecurrentvelocityandlimitittothemaximumvelocitythatcanbereached.Updatethepositionwiththevelocityperframe.

Thisfigureshowshowthesevectorsareaddedtoachievetheresultingpathtothetarget:

Page 165: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Thetruncate()functionlimitsthemagnitudeofavector,preventingthevelocityfromreachingagreatermodulethanallowed:

deftruncate(vector,m):

magnitude=abs(vector)

ifmagnitude>m:

vector*=m/magnitude

returnvector

Ifthevectormoduleisgreaterthanthemaximumvalue,thevectorisnormalizedandscaledbythisvalue.

Thecharacter’stargetwillbethemousepointer,andthetargetcoordinatesmustbeupdatedwhenthemouseismoved.Todoso,wewilldefineacustomlayerthathandlesthemousemotioneventsandsetstheactor’starget:

classMainLayer(cocos.layer.Layer):

is_event_handler=True

def__init__(self):

super(MainLayer,self).__init__()

self.actor=Actor(320,240)

self.add(self.actor)

defon_mouse_motion(self,x,y,dx,dy):

self.actor.target=eu.Vector2(x,y)

if__name__=='__main__':

cocos.director.director.init(caption='SteeringBehaviors')

scene=cocos.scene.Scene(MainLayer())

cocos.director.director.run(scene)

Whenyourunthiscode,whichispresentintheseek.pyscript,thecharacterwillseekthemousepointerwhenyoumoveitoverthewindow.

Page 166: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Forthefleebehavior,simplychangethesignofthevelocitywhenthepositionisupdated:

defupdate(self,dt):

ifself.targetisNone:

return

#...

self.position+=self.velocity*dt*-1

Thiswillmakethecharacterfleefromthemousepointer,andeventuallyleavethewindowifthemouseisclosertothecenterthanthecharacter.Intheseek_and_flee.pyscript,youcanfindacombinationoftheseimplementations,whereitispossibletoswitchthemwiththemouseclick.

Page 167: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ArrivalYoumaynoticethatwiththeseekbehavior,ifthecharacter’svelocityistoohigh,itwillpassthroughthetargetandcomebacksmoothly.

Thearrivalbehaviorlowersthevelocityoncethecharacterhasreachedaminimumdistanceclosetothetarget,decreasingitgradually.Thisdistancegivesustheradiusoftheslowingarea,acirclecenteredatthetargetpositionthatcausesthecharacter’svelocitytodecrease.

Theimplementationofthisisquitesimilartothatoftheseekbehavior.Weneedtoaddtheslow_radiusmembertoindicatetheradiusoftheslowingarea:

classActor(cocos.cocosnode.CocosNode):

def__init__(self,x,y):

super(Actor,self).__init__()

self.position=(x,y)

self.slow_radius=200

self.velocity=eu.Vector2(0,0)

#...

Withthisattribute,wecanmodifyourupdate()methodsothatthesteeringforceisdecreasedlinearlybytherampfactor.Whenthedistanceisgreaterthantheslowradius,therampfactorisataminimumof1.0,sothesteeringforceremainsunmodified:

defupdate(self,dt):

ifself.targetisNone:

return

distance=self.target-eu.Vector2(self.x,self.y)

ramp=min(abs(distance)/self.slow_radius,1.0)

steering=distance*self.speed*ramp-self.velocity

steering=truncate(steering,self.max_force)

self.velocity=truncate(self.velocity+steering,

Page 168: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self.max_velocity)

self.position+=self.velocity*dt

Notethatwhenthedistanceapproaches0,therampfactorequals0andthesteeringforcebecomes-self.velocity.

Checkoutthearrival.pyfilewiththecompleteimplementationofthebehavior.

Page 169: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PursuitandevadeThepursuitbehaviorissimilartoseek,withthedifferencebeingthatthecharacterwillmovetowardsthepositionatwhichthetargetwillbeinthefuturebasedonthetarget’scurrentvelocity.

Thefuturepositionisthesumofthetargetpositionplusthevelocityvectormultipliedbyaunitoftime.Itgivesthecoordinateswherethetargetwillbeplacedwithin1secondifitdoesnotmodifyitsvelocity:

defupdate(self,dt):

ifself.targetisNone:

return

pos=self.target.position

future_pos=pos+self.target.velocity*1

distance=future_pos-eu.Vector2(self.x,self.y)

steering=distance*self.speed-self.velocity

steering=truncate(steering,self.max_force)

self.velocity=truncate(self.velocity+steering,

self.max_velocity)

self.position+=self.velocity*dt

Ifyouwanttosetthefuturepositionasthecoordinateswherethetargetwillbewithin2seconds,multiplythetarget’svelocityby2.

Inthisexample,wewillreplaceourmousepointertargetwithamovingnode.Itwillhavealinearvelocity,andouractorcanusethisvelocitytocalculatethefutureposition:

classMainLayer(cocos.layer.Layer):

def__init__(self):

super(MainLayer,self).__init__()

self.target=ps.Sun()

Page 170: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self.target.position=(40,40)

self.target.start_color=ps.Color(0.2,0.7,0.7,1.0)

self.target.velocity=eu.Vector2(50,0)

self.add(self.target)

self.actor=Actor(320,240)

self.actor.target=self.target

self.add(self.actor)

self.schedule(self.update)

defupdate(self,dt):

self.target.position+=self.target.velocity*dt

Ontheotherhand,fortheevadebehavior,weneedtochangeonlythelaststatementoftheupdate()methodtoself.position+=self.velocity*dt*-1,exactlyaswedidforthefleebehavior.

Thecodeofthisbehaviorisincludedinthepursuit.pyscript.

Page 171: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

WanderThewandersteeringsimulatesarandomwalkwithoutanyspecifictarget.Itproducesacasualmovementwithoutsharpturnsorpredictablepaths.

Itcanbeimplementedwithaseekbehaviorwithtargetscalculatedrandomly.However,amoreorganicsolutionistogeneratearandomsteeringforcetowardsapointonacircumferenceplacedaheadofthecharacter.

Thisforceiscalculatedperframe,givingsmallrandomdisplacementsthatproducethevisualeffectofthecharacterwanderingaround.Thisbehaviorcanbeparameterizedwiththefollowingvalues:

wander_angle:Thecurrentangleofdisplacement,towhichthesmallvariationswillbeaddedcircle_distance:Thedistanceofthecharacter’spositionfromthewandercircle’scentercircle_radius:Theradiusofthewandercircleangle_change:Thefactorbywhichtherandomvaluewillbemultipliedtoproduceachangeinthewanderangle

Nowthatwehaveseenhowtheseforcesarecalculated,wecanimplementourwanderbehavior.

Firstofall,wewilladdtheseattributestoourActorclass:

importmath

importrandom

classActor(cocos.cocosnode.CocosNode):

def__init__(self,x,y):

super(Actor,self).__init__()

self.position=(x,y)

self.velocity=eu.Vector2(0,0)

self.wander_angle=0

Page 172: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self.circle_distance=50

self.circle_radius=10

self.angle_change=math.pi/4

self.max_velocity=50

self.add(ps.Sun())

self.schedule(self.update)

Withthesemembers,wecanmodifyourupdate()method:

defupdate(self,dt):

circle_center=self.velocity.normalized()*\

self.circle_distance

dx=math.cos(self.wander_angle)

dy=math.sin(self.wander_angle)

displacement=eu.Vector2(dx,dy)*self.circle_radius

self.wander_angle+=(random.random()-0.5)*\

self.angle_change

self.velocity+=circle_center+displacement

self.velocity=truncate(self.velocity,

self.max_velocity)

self.position+=self.velocity*dt

self.position=(self.x%640,self.y%480)

Thesenewstatementsperformthefollowingoperations:

Thecircle’scenterisplacedatthegivendistanceaheadofthecurrentcharacter’svelocity.Wecalculatethedisplacementwithwander_angle,anditisscaledaspertheradiusofthecircle.Givenarandomvaluebetween0.0and1.0,wesubtract0.5sothatthevalueisbetween-0.5and0.5,thuspreventingtheanglefromalwayschangingbyapositivevalue.Wemultiplythisvaluebyangle_changeandaddittothewander_anglemember.

Youmaynoticeanotherstatementafterthepositionisupdatedwiththevelocity.Thislaststatementofthemethodisusedtorelocatethecharacter,sinceitisverylikelythatthecharacterwillleavethescreenduetotherandomnessofitsmovement.

Feelfreetotweakthecircle_distance,circle_radius,andangle_changevalues,andobservethedifferencesthattheycausetotheplayer’smovement.

Ourmainlayerissimplifiedsinceitonlyneedstoaddtheactor:

classMainLayer(cocos.layer.Layer):

def__init__(self):

super(MainLayer,self).__init__()

self.actor=Actor(320,240)

self.add(self.actor)

Thewander.pyscriptcontainsthecompleteimplementationofthisbehavior.

Page 173: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ObstacleavoidanceSofar,wehavenotplacedanybarrierinourworld,sothecharactercanfreelymovearoundwithoutavoidinganyarea.

However,mostgamesplacesomeobstacles,andthecharacterscannotpassthroughthem.Theobstacleavoidancebehaviorgeneratesasteeringforcethatcausesthecharactertotryanddodgetheseblockingitems.Notethatthisdoesnotperformcollisiondetections,soifthecurrentspeedishighandthesteeringforcemoduleisnotenoughtocounteractthecharacter’svelocity,thecharactermightoverlapwiththeshapeoftheobstacle.

Todetectthepresenceofobstaclesahead,thecharacterwillkeepanimaginaryvectoralongitsforwardaxis,whichrepresentsthedistanceuptowhichitcanseeahead.Theclosestobstaclethatcollideswiththissegment,ifany,willbethethreattoavoid.

WewilldefineanObstacleclasstorepresentthecircularobstaclesthatwewilluse.Eachofthemcanhaveadifferentradius,andwewillkeeptrackofallthecreatedinstanceswithaclassmemberlist:

classObstacle(cocos.cocosnode.CocosNode):

instances=[]

def__init__(self,x,y,r):

super(Obstacle,self).__init__()

self.position=(x,y)

self.radius=r

particles=ps.Sun()

particles.size=r*2

particles.start_color=ps.Color(0.0,0.7,0.0,1.0)

self.add(particles)

self.instances.append(self)

TheActorclasswillhavetwonewattributes:max_ahead,themaximumdistanceatwhichthecharactercandetectthepresenceofanobstacle,andmax_avoid_force,themaximumforcethatcanbeappliedtododgeanobstacle:

classActor(cocos.cocosnode.CocosNode):

def__init__(self,x,y):

Page 174: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

super(Actor,self).__init__()

self.position=(x,y)

self.velocity=eu.Vector2(0,0)

self.speed=2

self.max_velocity=300

self.max_force=10

self.target=None

self.max_ahead=200

self.max_avoid_force=300

self.add(ps.Sun())

self.schedule(self.update)

Thesteeringforcekeepsseekingthetarget,withthedifferencethattheavoidforceisappliedaswell:

defupdate(self,dt):

ifself.targetisNone:

return

distance=self.target-eu.Vector2(self.x,self.y)

steering=distance*self.speed-self.velocity

steering+=self.avoid_force()

steering=truncate(steering,self.max_force)

self.velocity=truncate(self.velocity+steering,

self.max_velocity)

self.position+=self.velocity*dt

Theavoidforceiscalculatedwiththeclosestobstacle.Tocheckwhethertheaheadvectorintersectseachobstacle,wewillcalculatetheminimumdistancefromthecircle’scentertothevector.Ifthisdistanceislowerthanthecircle’sradiusanditistheminimumdistance,theobstacleistheclosestone.

Tofindthedistancebetweenavectorandapoint,wemustprojectthatpointtothevectorandseeifitfallsintoitslength.Ifitdoes,wecalculatethedistancebetweenthesetwopoints,whichisthedistancebetweenthevectorandthepoint.Otherwise,itmeansthatthepointisbehindorbeyondtheprojectionofthepointonthevector.

Withtheseoperationsinmind,wecandefinetheavoid_force()method:

defavoid_force(self):

Page 175: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

avoid=eu.Vector2(0,0)

ahead=self.velocity*self.max_ahead/self.max_velocity

l=ahead.dot(ahead)

ifl==0:

returnavoid

closest,closest_dist=None,None

forobjinObstacle.instances:

w=eu.Vector2(obj.x-self.x,obj.y-self.y)

t=ahead.dot(w)

if0<t<l:

proj=self.position+ahead*t/l

dist=abs(obj.position-proj)

ifdist<obj.radiusand\

(closestisNoneordist<closest_dist):

closest,closest_dist=obj.position,dist

ifclosestisnotNone:

avoid=self.position+ahead-closest

avoid=avoid.normalized()*self.max_avoid_force

returnavoid

Thesearethestepsperformedbythismethod:

Wecalculatetheaheadvectoranditssquaredlength.Ifitiszero,itmeansthatitdoesnotseeanythingaheadandtheavoidanceforceis(0,0).Foreachobstacle,wekeeptrackofthedistancebetweentheaheadvectorandthecircle’scenter.Ifthisdistanceislowerthanitsradiusanditistheclosestobstacle,wesetitastheobstacletoavoid.Finally,ifthereisanyobstacleselected,wescaletheavoidforcebythemax_avoid_forcefactor.

WewilladdsomeobstaclestoourMainLayer,andtheactorwillavoidthemwithoutanyfurtherreferencetothesenewobjects,sincetheyareautomaticallyaddedtotheObstacle.instanceslist:

classMainLayer(cocos.layer.Layer):

is_event_handler=True

def__init__(self):

super(MainLayer,self).__init__()

self.add(Obstacle(200,200,40))

self.add(Obstacle(240,350,50))

self.add(Obstacle(500,300,50))

self.actor=Actor(320,240)

self.add(self.actor)

defon_mouse_motion(self,x,y,dx,dy):

self.actor.target=eu.Vector2(x,y)

Youcancheckouttheentirescriptintheobstacles.pyfile.Runitandmovethemousepointerabovethescreentoseehowthecharactertriestoavoidtheobstaclesrepresentedbythegreencircles.

Page 176: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 177: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

GravitationgameToputintopracticewhatyouhavelearnedaboutsteeringbehaviors,wewillbuildabasicgame.Theplayer’sobjectiveistocollectsomepickupitemsthatrotatearounddifferentplanetsplacedintheworld,whileescapingfromenemies.Theenemiesarenon-playablecharactersthatseektheplayablecharacterandavoidtheplanets.

Wewillrepresentthecharactersasparticlesystems,andthefinalversionofthegamewilllooklikethis:

Page 178: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

BasicgameobjectsAsusual,wewilldefineabaseclassthatwrapstheaccesstotheCircleShapeattributewiththeupdatedcenter:

fromcocos.cocosnodeimportCocosNode

fromcocos.directorimportdirector

importcocos.collision_modelascm

classActor(CocosNode):

def__init__(self,x,y,r):

super(Actor,self).__init__()

self.position=(x,y)

self._cshape=cm.CircleShape(self.position,r)

@property

defcshape(self):

self._cshape.center=eu.Vector2(self.x,self.y)

returnself._cshape

Sincesomecharacterswillrotatearoundtheplanets,wewilldefineanotherclasstoupdatethepositionoftheactorbasedonarotationalmovement.Thisclasswillhaveareferencetotheplanetitisrotatingaround,theangularspeed,andthecurrentangleofrotation:

classMovingActor(Actor):

def__init__(self,x,y,r):

super(MovingActor,self).__init__(x,y,r)

self._planet=None

self._distance=0

self.angle=0

self.rotationSpeed=0.6

self.schedule(self.update)

AccesstothePlanetmemberwillbeimplementedthroughaproperty,becausewhenwesetplanet,weneedtocalculatethedistanceandthecurrentangle:

@property

defplanet(self):

returnself._planet

@planet.setter

defplanet(self,val):

ifvalisnotNone:

dx,dy=self.x-val.x,self.y-val.y

self.angle=-math.atan2(dy,dx)

self._distance=abs(eu.Vector2(dx,dy))

self._planet=val

Thescheduledmethodincrementstherotationanglewiththeelapsedtimeandupdatestheactor’spositionwiththecosineandsineofthisangle:

defupdate(self,dt):

ifself.planetisNone:

return

Page 179: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

dist=self._distance

self.angle+=self.rotationSpeed*dt

self.angle%=math.pi*2

self.x=self.planet.x+dist*math.cos(self.angle)

self.y=self.planet.y-dist*math.sin(self.angle)

Page 180: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PlanetsandpickupsOurPlanetclasswillrepresentthestaticactorsaroundwhichthepickupsandtheplayerrotate.Wewillkeeptrackofalltheinstances,aswedidwiththeObstacleclasspreviously:

classPlanet(Actor):

instances=[]

def__init__(self,x,y,r=50):

super(Planet,self).__init__(x,y,r)

particles=ps.Sun()

particles.start_color=ps.Color(0.5,0.5,0.5,1.0)

particles.size=r*2

self.add(particles)

self.instances.append(self)

Pickupswillbemovingactors,sowewillinheritfromMovingActor,whichhasimplementedthisfunctionality:

classPickupParticles(ps.Sun):

size=20

start_color=ps.Color(0.7,0.7,0.2,1.0)

classPickup(MovingActor):

def__init__(self,x,y,planet):

super(Pickup,self).__init__(x,y,10)

self.planet=planet

self.gravity_factor=50

self.particles=PickupParticles()

self.add(self.particles)

Page 181: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PlayerandenemiesTheplayercharacterrevolvesaroundtheplanetslikethepickupsdo,butwiththedifferencethatitcanswitchfromoneplanettoanotherwhenthespacebarispressed.

Thisisthecontrolthattheplayerhasovertheactor,sowewillneedtoaddalinearspeedtoimplementthiskindofmovement,whichextendsthelogicofferedbytheMovingActorclass:

classPlayer(MovingActor):

def__init__(self,x,y,planet):

super(Player,self).__init__(x,y,16)

self.planet=planet

self.rotationSpeed=1

self.linearSpeed=80

self.direction=eu.Vector2(0,0)

self.particles=ps.Meteor()

self.particles.size=50

self.add(self.particles)

Nowtheupdate()methodperformsalinearmovementwhentheplayerisnotrevolvingaroundanyplanet:

defupdate(self,dt):

ifself.planetisnotNone:

super(Player,self).update(dt)

gx=20*math.cos(self.angle)

gy=20*math.sin(self.angle)

self.particles.gravity=eu.Point2(gx,-gy)

else:

self.position+=self.direction*dt

Finally,weneedtoimplementthemethodthattriggerstheswitchfromoneplanettoanother.Itcalculatesthedirectionvectordependingonthecurrentangleofrotationwithrespecttotheplanet:

defswitch(self):

new_dir=eu.Vector2(self.y-self.planet.y,

self.planet.x-self.x)

self.direction=new_dir.normalized()*self.linearSpeed

self.planet=None

self.particles.gravity=eu.Point2(-self.direction.x,

-self.direction.y)

Youmaynoticethatthegravityoftheparticlesisupdatedwiththeplayer’smovement.Thisgivesvisualfeedbackofthecurrentdirectionofthecharacterandthepositionitispointingto.

TheimplementationoftheEnemyclassisalmostthesameastheonewesawwiththeobstacleavoidancebehavior.ThemaindifferencesarethatthisclassusesthePlanet.instances:listandtheradiusoftheobstaclesisretrievedfromthecshapememberofeachplanet:

defavoid_force(self):

Page 182: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

#...

closest,closest_dist=None,None

forobjinPlanet.instances:

w=eu.Vector2(obj.x-self.x,obj.y-self.y)

t=ahead.dot(w)

if0<t<l:

proj=self.position+ahead*t/l

dist=abs(obj.position-proj)

ifdist<obj.cshape.rand\

(closestisNoneordist<closest_dist):

closest,closest_dist=obj.position,dist

ifclosestisnotNone:

avoid=self.position+ahead-closest

avoid=avoid.normalized()*self.max_avoid_force

returnavoid

Page 183: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ExplosionsTherearesomecollisionsthatcantriggeragameevent.Forinstance,whenanitemispickedupbytheplayer,itmustberemoved,orwhentheplayablecharacterhitsanenemyoraplanet,itisdestroyedandspawnsagaininitsoriginalposition.

Onewaytoenhancethesesituationsisthroughexplosions;theygivevisualfeedbacktotheplayer.Wewillimplementthemwithacustomparticlesystem:

classActorExplosion(ps.ParticleSystem):

total_particles=400

duration=0.1

gravity=eu.Point2(0,0)

angle=90.0

angle_var=360.0

speed=40.0

speed_var=20.0

life=3.0

life_var=1.5

emission_rate=total_particles/duration

start_color_var=ps.Color(0.0,0.0,0.0,0.2)

end_color=ps.Color(0.0,0.0,0.0,1.0)

end_color_var=ps.Color(0.0,0.0,0.0,0.0)

size=15.0

size_var=10.0

blend_additive=True

def__init__(self,pos,particles):

super(ActorExplosion,self).__init__()

self.position=pos

self.start_color=particles.start_color

Page 184: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThegamelayerAllthegameobjectsthatwehavedevelopedwillbeaddedtothemaingamelayer.Asusual,itwillcontainacollisionmanagerthatcoverstheentirewindow:

classGameLayer(cocos.layer.Layer):

def__init__(self):

super(GameLayer,self).__init__()

x,y=director.get_window_size()

cell_size=32

self.coll_man=cm.CollisionManagerGrid(0,x,\

0,y,cell_size,cell_size)

self.planet_area=400

planet1=self.add_planet(450,280)

planet2=self.add_planet(180,200)

planet3=self.add_planet(270,440)

planet4=self.add_planet(650,480)

planet5=self.add_planet(700,150)

self.add_pickup(250,250,planet2)

self.add_pickup(740,480,planet4)

self.add_pickup(700,60,planet5)

self.player=Player(300,350,planet3)

self.add(self.player)

self.add(Enemy(600,100,self.player))

self.schedule(self.game_loop)

Wealsodefinedacoupleofhelpermethodstoavoidrepeatingthesamecodeduringinitialization:

defadd_pickup(self,x,y,target):

pickup=Pickup(x,y,target)

self.add(pickup)

defadd_planet(self,x,y):

planet=Planet(x,y)

self.add(planet)

returnplanet

Oncethescenarioissetupwiththepositionsofthecharacters,andwhatactorsarerotatingaroundeachplanet,wecanimplementthegameloop:

defgame_loop(self,_):

self.coll_man.clear()

fornodeinself.get_children():

ifisinstance(node,Actor):

self.coll_man.add(node)

ifself.player.is_running:

self.process_player_collisions()

defprocess_player_collisions(self):

player=self.player

forobjinself.coll_man.iter_colliding(player):

ifisinstance(obj,Pickup):

self.add(ActorExplosion(obj.position,

obj.particles))

Page 185: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

obj.kill()

else:

self.add(ActorExplosion(player.position,

player.particles))

player.kill()

Thelayermustprocesstheinputevents,specificallythespacebarpressthattriggerswhentheplayer’scharacterstopsrotatingaroundaplanetandleavestheorbitinaperpendiculardirection.

Whenthespacebarispressedagain,thecollisionmanagercalculateswhattheclosestplanetiswithinaspecificdistance—whichwesetupinthe__init__method—andifthereisany,itissetastheplanetaroundwhichthetargetmustrotate:

is_event_handler=True

defon_key_press(self,k,_):

ifk!=key.SPACE:

return

ifself.player.planetisNone:

self.player.planet=self.find_closest_planet()

else:

self.player.switch()

deffind_closest_planet(self):

ranked=self.coll_man.ranked_objs_near(self.player,

self.planet_area)

planet=next(filter(lambdax:isinstance(x[0],Planet),

ranked))

returnplanet[0]ifplanetisnotNoneelseNone

Finally,donotforgettoaddtheconditionalblocktorunthescriptasthemainmodule.Here,wesetthewindow’swidthandheight:

if__name__=='__main__':

director.init(width=850,height=600,caption='Gravitation')

director.run(cocos.scene.Scene(GameLayer()))

Youcancheckoutthecompletegameinthegravitation.pyscript.Thegamedoesnotrequireanyextraassets,sincealltherenderedelementsarebasedonthecocos2dparticlesystems.

Page 186: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 187: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SummaryInthischapter,youlearnedseveralkindsofsteeringbehaviorthatproducerealisticandautonomousnavigationsaroundyourgameworld.Thesestrategiesmightbemixed,causingcomplexpatternsandseeminglymoreintelligentactions.

Trytochangethevaluesoftheconstantsusedineachbehaviortoseehowtoachievedifferentvelocitiesandforces.Keepinmindtheimportanceofgeneratingsmoothmovementsinsteadofpronouncedtwistssothattheexperienceisvisuallyengaging.

Finally,weappliedthisknowledgeinabasicgame.Sinceitisademonstrationofaddinganon-playablecharacter,thegamecanbecomplementedwithalltheingredientsthatwecoveredinthepreviouscharacter(suchasmenus,transitions,andsoon).

Inthenextchapter,wewilljumpinto3DgamedevelopmentwithOpenGL,anewtopicwithmoreadvancedfeaturesanddetailsofalowerlevel.

Page 188: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 189: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Chapter5.Pygameand3DInourpreviouschapters,wedevelopedour2DgameswithPythonmodulesthatarebuiltontopofagraphicaluserinterfacelibrary,suchasTkinterandPyglet.Thisallowedustostartcodingourgameswithoutworryingaboutthelower-leveldetails.

Nowwewilldevelopourfirst3DgamewithPython,whichwillrequireanunderstandingofsomebasicprinciplesofOpenGL,apopularmultiplatformAPIforbuilding2Dand3Dapplications.YouwilllearnhowtointegratetheseprogramswithPygame,aPythonlibrarycommonlyusedtocreatesprite-basedgames.

Inthischapter,wewillcoverthefollowingtopics:

AsteadyapproachtoPyOpenGLandPygameInitializinganOpenGLcontextUnderstandingthedifferentmodesthatcanbeenabledwithOpenGLHowtorenderlightsandsimpleshapesIntegratingOpenGLwithPygameDrawingprimitivesandperformanceimprovements

Page 190: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

InstallingpackagesPyOpenGLisapackagethatoffersPythonbindingstoOpenGLandrelatedAPIs,suchasGLUandGLUT.ItisavailableonthePythonpackageIndex,soyoucaneasilyinstallitviapip:

$pipinstallPyOpenGL

However,wewillneedfreeglutforourfirstexamples,beforeweintegrateOpenGLwithPygame.Freeglutisathird-partylibrarythatisnotincludedifyouinstallthepackagefromPyPI.

OnWindows,analternativeistodownloadandinstallthecompiledbinariesfromhttp://www.lfd.uci.edu/~gohlke/pythonlibs/#pyopengl.RemembertoinstalltheversionforPython3.4.

Pygameistheotherpackagethatwewillneedinthischapter.Itcanbedownloadedfromtheofficialwebsiteathttp://www.pygame.org/download.shtml.Youcaninstallitfromsourceifyouwantto;thecompilationpagecontainsthestepsforbuildingPygameondifferentplatforms.

WindowsuserscandirectlyusetheMSIforPython3.2ordownloadUnofficialWindowsBinariesfromtheChristophGohlke’swebsite(http://www.lfd.uci.edu/~gohlke/pythonlibs/).

MacintoshuserscanfindtheinstructionsrequiredtocompileitfromsourceonthePygamewebsiteathttp://pygame.org/wiki/macintosh.

Page 191: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 192: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

GettingstartedwithOpenGLOpenGLisabroadtopicinitself,anditispossibletofindplentyoftutorials,books,andotherresources,usuallytargetedatCorC++.

Sincethischapterisnotintendedtobeacomprehensiveguideforthisspecification,wewilltakeadvantageofGLUT,whichstandsforOpenGLUtilityToolkit.Itiswidelyusedinsmallapplicationsbecauseofitssimplicityandportability,andthebindingsareimplementedinPyOpenGL.

GLUTwillhelpusperformsomebasicoperations,suchascreatingwindowsandhandlinginputevents.

TipGLUTlicensing

Unfortunately,GLUTisnotinthepublicdomain.Thecopyrightismaintainedbyitsauthor,MarkKilgard,whowroteitforthesampleprogramsincludedinRedBook,theofficialOpenGLprogrammingguide.

Thisisthereasonweareusingfreeglut,oneoftheopensourcealternativesthatimplementtheGLUTAPI.

Page 193: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

InitializingthewindowThefirstlinesofourscriptwillbetheimportstatementsaswellasthedefinitionofourAppclassandits__init__method.

ApartfromtheOpenGLAPIandGLUT,weimporttheOpenGLUtilityLibrary(GLU).GLUisusuallydistributedwiththebasicOpenGLpackage,andwewilluseacoupleoffunctionsofferedbythislibraryinourexample:

importsys

importmath

fromOpenGL.GLimport*

fromOpenGL.GLUimport*

fromOpenGL.GLUTimport*

classApp(object):

def__init__(self,width=800,height=600):

self.title=b'OpenGLdemo'

self.width=width

self.height=height

self.angle=0

self.distance=20

Youmaywonderwhatthebbeforethe'OpenGLdemo'stringmeans.Itrepresentsabinarystring,anditisoneofthedifferencesbetweenPython2and3.Therefore,ifyoufindaGLUTprogramwritteninPython2,rememberthatthestringtitleofthewindowmustbedefinedasabinarystringinordertoworkwithPython3.

Withtheseinstancemembers,wecancallourOpenGLinitializationfunctions:

defstart(self):

glutInit()

glutInitDisplayMode(GLUT_DOUBLE|GLUT_DEPTH)

glutInitWindowPosition(50,50)

glutInitWindowSize(self.width,self.height)

glutCreateWindow(self.title)

glEnable(GL_DEPTH_TEST)

glEnable(GL_LIGHTING)

glEnable(GL_LIGHT0)

Stepbystep,ourstartmethodperformsthefollowingoperations:

glutInit():ThisinitializestheGLUTlibrary.Whileitispossibletopassparameterstothisfunction,wewillleavethiscallwithoutanyarguments.glutInitDisplayMode():Thissetsthedisplaymodeofthetop-levelwindowthatwewillcreate.ThemodeisthebitwiseORofafewGLUTdisplaymodemasks.GLUT_DOUBLEisthemodeforthedoublebuffer,whichcreatesseparatefrontandbackbuffers.Whileoneofthesebuffersisbeingdisplayed,theotheroneisbeingrendered.Ontheotherhand,GLUT_DEPTHrequestsadepthbufferforthewindow.Itstoresthezcoordinateofeachgeneratedpixel,andifthesamepixelisrenderedforasecondtimebecausetwoobjectsoverlap,itdetermineswhichobjectisclosertothe

Page 194: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

camera,thatis,reproducingthedepthperception.glutInitWindowPosition()andglutInitWindowsSize():Thesesettheinitialpositionofthewindowanditssize.Accordingtoourwidthandheightinstancemembers,itindicatestocreateawindowof800x600pixelswithanoffsetof50pixelsinthexandyaxesfromthetop-leftcornerofthescreen.glutCreateWindow():Thiscreatesthetop-levelwindowofourapplication.Theargumentpassedtothisfunctionisabinarystringforuseasthewindowtitle.glEnable():ThisisthefunctionusedtoenabletheGLcapabilities.Inourapp,wecallitwiththefollowingvalues:

GL_DEPTH_TEST:Thisperformsdepthcomparisonsandupdatesthedepthbuffer.GL_LIGHTING:Thisenableslighting.GL_LIGHT0:ThisenablesLight0.PyOpenGLdefinesaspecificnumberoflightconstants—fromGL_LIGHT0toGL_LIGHT8—buttheparticularimplementationofOpenGLthatyouarerunningmightallowmorethanthisnumber.

TipLightingandcolors

Whenlightingisenabled,thecolorsarenotdeterminedbytheglColorfunctionsbutbythecombinationofthelightingcomputationandthematerialcolorssetbyglMaterial.TocombinelightingwithglColor,itisrequiredthatyouenableGL_COLOR_MATERIALfirst:

glEnable(GL_COLOR_MATERIAL)

#...

glColor4f(r,g,b,a)

#Drawpolygons

OncewehaveinitializedGLUTandenabledtheGLcapabilities,wecompleteourstart()methodbyspecifyingtheclearcolor,settingtheperspective,andstartingthemainloop:

defstart(self):

#...

glClearColor(.1,.1,.1,1)

glMatrixMode(GL_PROJECTION)

aspect=self.width/self.height

gluPerspective(40.,aspect,1.,40.)

glMatrixMode(GL_MODELVIEW)

glutDisplayFunc(self.display)

glutSpecialFunc(self.keyboard)

glutMainLoop()

defkeyboard(self,key,x,y):

pass

Thesestatementsperformthefollowingoperations:

glClearColor():Thisdefinestheclearvaluesforthecolorbuffer;thatis,eachpixelwillhavethisvalueifnoothercolorisrenderedinthispixel.glMatrixMode():Thissetsthematrixstackmodeformatrixoperations,inthiscase

Page 195: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

totheprojectionmatrixstack.OpenGLconcatenatesmatrixoperationsforhierarchicalmodes,makingiteasytocomposethetransformationofachildobjectrelativetoitsparent.WithGL_PROJECTION,wesetthematrixmodefortheprojectionmatrixstack.gluPerspective():Thepreviousstatementsetstheprojectionmatrixstackasthecurrentstack.Withthisfunction,wecangeneratetheperspectiveprojectionmatrix.Theparametersthatgeneratethismatrixareasfollows:

fovy:Theviewangleindegreesintheydirection.aspect:Thisistheaspectratioofthefieldofview.Itistheratiooftheviewportwidthtotheviewportheight.zNear:ThedistancefromtheviewertotheNearplane.zFar:ThedistancefromtheviewertotheFarplane.

WithglMatrixMode(GL_MODELVIEW),wesetthemodelviewmatrixstack,whichistheinitialvalue,asthecurrentmatrixmode.

ThelastthreeGLUTcallsdothefollowing:

glutDisplayFunc():Thisreceivesthefunctionthatwillbeinvokedtodisplaythewindow.

Page 196: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

glutSpecialFunc():Thissetsthekeyboardcallbackforthecurrentwindow.NotethatthiscallbackwillbetriggeredonlywhenthekeysrepresentedbytheGLUT_KEY_*constantsarepressed.glutMainLoop():Thisstartsthemainloopoftheapplication.

WiththeOpenGLcontextinitialized,weareabletocalltheOpenGLfunctionsthatwillrenderourscene.

TipTheOpenGLandGLUTreference

Asyoumayhavealreadynoticed,theOpenGLandGLUTspecificationsdefinealargenumberoffunctions.YoucanfindthebindingsoftheseAPIsimplementedbyPyOpenGLontheofficialwebsiteathttp://pyopengl.sourceforge.net/documentation/manual-3.0/index.html.

Page 197: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

DrawingshapesOurdisplay()functionperformstheverycommontasksofamaingameloop.

Itfirstclearsthescreen,thensetsupaviewingtransformation(wewillseewhatthismeansafterthesnippet),andfinallyrendersthelightanddrawsthegameobjects:

defdisplay(self):

x=math.sin(self.angle)*self.distance

z=math.cos(self.angle)*self.distance

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

glLoadIdentity()

gluLookAt(x,0,z,

0,0,0,

0,1,0)

glLightfv(GL_LIGHT0,GL_POSITION,[15,5,15,1])

glLightfv(GL_LIGHT0,GL_DIFFUSE,[1.,1.,1.,1.])

glLightfv(GL_LIGHT0,GL_CONSTANT_ATTENUATION,0.1)

glLightfv(GL_LIGHT0,GL_LINEAR_ATTENUATION,0.05)

glPushMatrix()

glMaterialfv(GL_FRONT,GL_DIFFUSE,[1.,1.,1.,1.])

glutSolidSphere(2,40,40)

glPopMatrix()

glPushMatrix()

glTranslatef(4,2,0)

glMaterialfv(GL_FRONT,GL_DIFFUSE,[1.,0.4,0.4,1.0])

glutSolidSphere(1,40,40)

glPopMatrix()

glutSwapBuffers()

Thesearetheoperationsthatdisplay()performs:

glClear():WiththeGL_COLOR_BUFFER_BITandGL_DEPTH_BUFFER_BITmasks,thisclearsthecoloranddepthbuffers.glLoadIdentity():Thisloadstheidentitymatrixasthecurrentmatrix.Theidentitymatrixisa4x4matrixwithonesinthemaindiagonalandzeroseverywhereelse.Thismakesthestackmatrixstartoverattheorigin,whichisusefulifyouhavepreviouslyappliedsomematrixtransformations.gluLookAt():Thiscreatesaviewingmatrix.Thefirstthreeparametersarethex,y,andzcoordinatesoftheeyepoint.Thenextthreeparametersarethex,y,andzcoordinatesofthereferencepoint,thatis,thepositionthecameraislookingat.Finally,thelastthreeparametersspecifythedirectionoftheupvector(usually,itis0,1,0).glLightfv():Thissetstheparametersoflightsource0(GL_LIGHT0).Thefollowingparametersarespecifiedinourexample:

GL_POSITION:Thisdefinesthepositionofthelight

Page 198: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

GL_DIFFUSE:ThissetstheRGBAintensityofthelightGL_CONSTANT_ATTENUATION:ThisspecifiestheconstantattenuationfactorGL_LINEAR_ATTENUATION:Thisspecifiesthelinearattenuationfactor

Oncethelightingattributesareset,wecanstartrenderingbasicshapeswithGLUT.Ifwedrawtheobjectsfirst,lightingwillnotbeappliedcorrectly:

glPushMatrix():Thispushesanewmatrixintothecurrentmatrixstack,identicaltotheonebelowit.Whilewedothis,wecanapplytransformationssuchasglTranslateandglRotate,tothismatrix.Wewillrenderourfirstsphereattheorigin,butthesecondonewillbetransformedwithglTranslate.glTranslate():Thismultipliesthecurrentmatrixbythetranslationmatrix.Inourexample,thetranslationvaluesforthesecondsphereare4forthexaxis,and2fortheyaxis.glMaterialfv():Thissetsthematerialparametersofthefrontface,asitiscalledwithGL_FONT.WithGL_DIFFUSE,wespecifythatwearesettingtheRGBAreflectanceofthematerial.glutSolidSphere():ThroughGLUT,thisroutineallowsustoeasilydrawasolidsphere.Itreceivesthesphere’sradiusasthefirstargument,andthenumberofslicesandstacksintowhichthespherewillbesubdivided.Thegreaterthesevaluesare,therounderthespherewillbe.glPopMatrix():Thispopsthecurrentmatrixfromthestack.Ifwedidnotdothis,eachnewobjectrenderedwouldbeachildofthepreviousone.

Finally,weswitchthebufferswithglutSwapBuffers().Ifdoublebufferingwasnotenabled,weshouldcallthesinglebufferequivalent—glFlush().

Page 199: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

RunningthedemoAsusual,wecheckwhetherthemoduleisthemainscriptforstartingtheapplication:

if__name__=='__main__':

app=App()

app.start()

Ifyourunthecompleteapplication,theresultwilllooklikewhatisshowninthefollowingscreenshot:

Page 200: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

RefactoringourOpenGLprogramAsyoumayhaveseen,thisexampleusesenoughOpenGLcallstogrowoutofcontrolifwedonotstructureourcode.That’swhywearegoingtoapplysomeobject-orientedprinciplestoachieveabetterorganization,withoutmodifyingtheorderofthecallsorlosinganyfunctionality.

ThefirststepwillbetodefineaLightclass.Itwillholdtheattributesneededtorenderthelight:

classLight(object):

enabled=False

colors=[(1.,1.,1.,1.),(1.,0.5,0.5,1.),

(0.5,1.,0.5,1.),(0.5,0.5,1.,1.)]

def__init__(self,light_id,position):

self.light_id=light_id

self.position=position

self.current_color=0

Besides,thismodularizationwillhelpusimplementanewfunctionality:changingthecolorofthelight.Wesetthecurrentcolorindexto0,andwewilliterateoverthedifferentcolorsdefinedinLight.colorseachtimetheswitch_color()methodiscalled.

Therender()methodrespectstheoriginalimplementationoflightingfromournon-refactoredversion:

defrender(self):

light_id=self.light_id

color=Light.colors[self.current_color]

glLightfv(light_id,GL_POSITION,self.position)

glLightfv(light_id,GL_DIFFUSE,color)

glLightfv(light_id,GL_CONSTANT_ATTENUATION,0.1)

glLightfv(light_id,GL_LINEAR_ATTENUATION,0.05)

defswitch_color(self):

self.current_color+=1

self.current_color%=len(Light.colors)

Finally,wewrapthecalltoenablelightingwiththeenable()methodandaclassattribute:

defenable(self):

ifnotLight.enabled:

glEnable(GL_LIGHTING)

Light.enabled=True

glEnable(self.light_id)

AnotherimprovementisthecreationofaSphereclass.Thisclasswillallowustocustomizetheradius,position,andcolorofeachinstance:

classSphere(object):

slices=40

stacks=40

Page 201: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

def__init__(self,radius,position,color):

self.radius=radius

self.position=position

self.color=color

defrender(self):

glPushMatrix()

glTranslatef(*self.position)

glMaterialfv(GL_FRONT,GL_DIFFUSE,self.color)

glutSolidSphere(self.radius,Sphere.slices,Sphere.stacks)

glPopMatrix()

Withtheseclasses,wecanadaptourAppclassandcreatetheinstancesthatwewillrenderinthemainloop:

classApp(object):

def__init__(self,width=800,height=600):

#...

self.light=Light(GL_LIGHT0,(15,5,15,1))

self.sphere1=Sphere(2,(0,0,0),(1,1,1,1))

self.sphere2=Sphere(1,(4,2,0),(1,0.4,0.4,1))

Rememberthatbeforerenderingthelightobject,weneedtoenableOpenGLlightingthroughthelight.enable()method:

defstart(self):

#...

glEnable(GL_DEPTH_TEST)

self.light.enable()

#...

Nowthedisplay()methodbecomessuccinctandexpressive,sincetheapplicationdelegatestheOpenGLcallstotheobjectinstances:

defdisplay(self):

x=math.sin(self.angle)*self.distance

z=math.cos(self.angle)*self.distance

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

glLoadIdentity()

gluLookAt(x,0,z,

0,0,0,

0,1,0)

self.light.render()

self.sphere1.render()

self.sphere2.render()

glutSwapBuffers()

Tocompleteourfirstsampleapplication,wewilladdinputhandling.Itallowstheplayertorotatethecameraaroundthespheresandmoveforwardorawayfromthecenterofthescene.

Page 202: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ProcessingtheuserinputAswesawearlier,glutSpecialFunctakesacallbackfunctionthatreceivesthepressedkeyasthefirstargument,andthexandycoordinatesofthemousewhenthekeywaspressed.

Wewillusetherightandleftarrowkeystomovearoundthespheres,andtheupanddownarrowkeystoapproximateormoveawayfromthespheres.Besidesallofthis,thecolorofthelightwillchangeifwepressF1,andtheapplicationwillbeclosediftheInsertkeyispressed.

Todoso,wewillcheckthevaluesofthekeyargumentwiththerespectiveGLUTconstants:

defkeyboard(self,key,x,y):

ifkey==GLUT_KEY_INSERT:

sys.exit()

ifkey==GLUT_KEY_UP:

self.distance-=0.1

ifkey==GLUT_KEY_DOWN:

self.distance+=0.1

ifkey==GLUT_KEY_LEFT:

self.angle-=0.05

ifkey==GLUT_KEY_RIGHT:

self.angle+=0.05

ifkey==GLUT_KEY_F1:

self.light.switch_color()

self.distance=max(10,min(self.distance,20))

self.angle%=math.pi*2

glutPostRedisplay()

Notethatwetrimmedthevalueoftheself.distancemember,soitsvalueisalwaysbetween10and20,andself.angleisalsoalwaysbetween0and2π.Tonotifythatthecurrentwindowneedstoberedisplayed,wecallglutPostRedisplay().

YoucancheckouttheChapter5_02.pyscript,whichcontainsthisrefactoredversionofourapplication.

Whenyourunit,pressthearrowkeystorotatearoundthespheresandF1toseehowthelightingaffectsthespheres’materials.

Page 203: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 204: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 205: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AddingthePygamelibraryWithGLUT,wecanwriteOpenGLprogramsquickly,primarilybecauseitwasaimedtoprovideroutinesthatmakelearningOpenGLeasier.However,theGLUTAPIwasdiscontinuedin1998.Nonetheless,therearesomepopularsubstitutesinthePythonecosystem.

Pygameisoneofthesealternatives,andwewillseethatitcanbeseamlesslyintegratedwithOpenGL,evensimplifyingtheresultingcodeforthesameprogram.

Page 206: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Pygame101BeforeweintegratePygameintoourOpenGLprogram,wewillwriteasample2DapplicationtogetstartedwithPygame.

WewillimportPygameanditslocalsmodule,whichincludestheconstantsthatwewillneedinourapplication:

importsys

importpygame

frompygame.localsimport*

classApp(object):

def__init__(self,width=400,height=300):

self.title='Hello,Pygame!'

self.fps=100

self.width=width

self.height=height

self.circle_pos=width/2,height/2

Pygameusesregularstringsforthewindowtitle,sowewilldefinetheattributewithoutaddingb.Anotherchangeisthenumberofframespersecond(FPS),whichwewilllaterfindouthowtocontrolviaPygame’sclock:

defstart(self):

pygame.init()

size=(self.width,self.height)

screen=pygame.display.set_mode(size,DOUBLEBUF)

pygame.display.set_caption(self.title)

clock=pygame.time.Clock()

whileTrue:

dt=clock.tick(self.fps)

foreventinpygame.event.get():

ifevent.type==QUIT:

pygame.quit()

sys.exit()

pressed=pygame.key.get_pressed()

x,y=self.circle_pos

ifpressed[K_UP]:y-=0.5*dt

ifpressed[K_DOWN]:y+=0.5*dt

ifpressed[K_LEFT]:x-=0.5*dt

ifpressed[K_RIGHT]:x+=0.5*dt

self.circle_pos=x,y

screen.fill((0,0,0))

pygame.draw.circle(screen,(0,250,100),

(int(x),int(y)),30)

pygame.display.flip()

WeinitializethePygamemoduleswithpygame.init(),andthenwecreateascreenwithagivenwidthandheight.TheDOUBLEBUFflagispassedsoastoenabledoublebuffering,whichhasthebenefitswementionedpreviously.

Themaineventloopisimplementedwithawhileblock,andwiththeClockinstance,wecancontroltheframerateandcalculatetheelapsedtimebetweenframes.Thisvaluewill

Page 207: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

bemultipliedbythespeedofmovement,sothecirclewillmoveatthesamespeediftheFPSvaluechanges.

Withpygame.event.get(),weretrievetheeventqueue,andifaQUITeventoccurs,thewindowisclosedandtheapplicationfinishesitsexecution.

Thepygame.key.get_pressed()returnsalistwiththepressedkeys,andwiththekeyconstants,wecancheckwhetherthearrowkeysarepressed.Ifso,thecircle’spositionisupdatedanditisdrawnonthenewcoordinates.

Finally,pygame.display.flip()updatesthescreen’ssurface.

TheChapter5_03.pyscriptcontainsthefullcodeofthisexample.

TipThePygamedocumentation

SincePygameisdividedintoseveralmodules,eachonewithvariousfunctions,classes,andconstants,theofficialdocumentationisausefulreference.

Weareusingsomefunctionsfromthekeymodule;youcanfindfurtherinformationaboutitathttps://www.pygame.org/docs/ref/key.html.Thesameappliesforthedisplayandtimemodules.

Page 208: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PygameintegrationLet’sseehowitispossibletoimplementthesamefunctionalitywithPygame.ThefirststepistoreplacetheOpenGL.GLUTimportwiththeonesweusedinourpreviousexample:

importsys

importmath

importpygame

frompygame.localsimport*

fromOpenGL.GLimport*

fromOpenGL.GLUimport*

Thetitlestringisnowaregularstring,andtheFPSattributecanbeaddedaswell:

classApp(object):

def__init__(self,width=800,height=600):

self.title='OpenGLdemo'

self.fps=60

self.width=width

self.height=height

#...

WeremovetheGLUTcallsfromourstart()method,andtheyarereplacedbythePygameinitialization.ApartfromDOUBLEBUF,wewilladdtheOPENGLflagtocreateanOpenGLcontext:

defstart(self):

pygame.init()

pygame.display.set_mode((self.width,self.height),

OPENGL|DOUBLEBUF)

pygame.display.set_caption(self.title)

glEnable(GL_CULL_FACE)

#...

glMatrixMode(GL_MODELVIEW)

clock=pygame.time.Clock()

whileTrue:

dt=clock.tick(self.fps)

self.process_input(dt)

self.display()

Thenewprocess_input()methodupdatesthesceneandtheinstanceattributesbyretrievingtheeventsfromtheeventqueueandprocessingthepressedkeys.

IfaQUITeventoccursortheEsckeyispressed,thePygameprogramisexecuted.Otherwise,thecamerapositionisupdatedwiththedistanceandangleofrotation,controlledbythearrowkeys:

defprocess_input(self,dt):

foreventinpygame.event.get():

ifevent.type==QUIT:

self.quit()

Page 209: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ifevent.type==KEYDOWN:

ifevent.key==K_ESCAPE:

self.quit()

ifevent.key==K_F1:

self.light.switch_color()

pressed=pygame.key.get_pressed()

ifpressed[K_UP]:

self.distance-=0.01*dt

ifpressed[K_DOWN]:

self.distance+=0.01*dt

ifpressed[K_LEFT]:

self.angle-=0.005*dt

ifpressed[K_RIGHT]:

self.angle+=0.005*dt

self.distance=max(10,min(self.distance,20))

self.angle%=math.pi*2

TheglutSwapBuffers()isreplacedbypygame.display.flip(),andthenewquit()methodquitsPygameandexitsPythongracefully:

defdisplay(self):

#...

self.light.render()

self.sphere1.render()

self.sphere2.render()

pygame.display.flip()

defquit(self):

pygame.quit()

sys.exit()

AnotherconsequenceofremovingGLUTisthatwecannotuseglutSolidSpheretorenderourspheres.

Fortunately,wecansubstituteitwiththegluSphereGLUfunction.TheonlydifferenceisthatweneedtocreateaGLUquadraticobjectfirst,andthencallthefunctionwiththisargumentandtheusualradius,numberofslices,andnumberofstacksintowhichthesphereisdivided:

classSphere(object):

slices=40

stacks=40

def__init__(self,radius,position,color):

self.radius=radius

self.position=position

self.color=color

self.quadratic=gluNewQuadric()

defrender(self):

glPushMatrix()

glTranslatef(*self.position)

glMaterialfv(GL_FRONT,GL_DIFFUSE,self.color)

gluSphere(self.quadratic,self.radius,

Page 210: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Sphere.slices,Sphere.stacks)

glPopMatrix()

Withthesechanges,theGLUTAPIisnowcompletelyreplacedbyPygame.Checkoutthechapter5_04.pyscripttoseethecompleteimplementation.

TipOpenGLandSDL

ByincludingPygame,wereplacetheGLUTAPIwithSimpleDirectMediaLayer(SDL),whichisthelibrarythatPygameisbuiltover.Likefreeglut,itisanothercross-platformalternativetoGLUT.

Page 211: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 212: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

DrawingwithOpenGLUntilnow,wehavealwaysrenderedourobjectswithautilityroutine,butmostOpenGLapplicationsrequiretheuseofsomedrawingprimitives.

Page 213: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TheCubeclassWewilldefineanewclasstorendercubes,andwewillusethefollowingrepresentationtobetterunderstandthevertices’positions.From0to7,theverticesareenumeratedandrepresentedina3Dspace.

Thesidesofacubecannowberepresentedastuples:thebackfaceis(0,1,2,3),therightfaceis(4,5,1,0),andsoon.

Notethatwearrangetheverticesincounterclockwiseorder.Aswewilllearnlater,thiswillhelpusenableanoptimizationcalledfaceculling,whichconsistsofdrawingonlythevisiblefacesofapolygon:

classCube(object):

sides=((0,1,2,3),(3,2,7,6),(6,7,5,4),

(4,5,1,0),(1,5,7,2),(4,0,3,6))

The__init__methodwillstorethevaluesofthepositionandcolor,aswellasthevertexcoordinateswithrespecttothecenterpositionofthecube:

def__init__(self,position,size,color):

self.position=position

self.color=color

x,y,z=map(lambdai:i/2,size)

Page 214: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self.vertices=(

(x,-y,-z),(x,y,-z),

(-x,y,-z),(-x,-y,-z),

(x,-y,z),(x,y,z),

(-x,-y,z),(-x,y,z))

Therender()methodpushesanewmatrix,transformsitaccordingtoitscurrentposition,andcallsglVertex3fv()foreachvertexofthesixfacesofcube.

TheglVertex3fvtakesalistofthreefloatvaluesthatspecifythevertexposition.ThisfunctionisexecutedbetweentheglBegin()andglEnd()calls.Theydelimittheverticesthatdefineaprimitive.TheGL_QUADSmodetreatseachgroupoffourverticesasanindependentquadrilateral.

Thelaststatementpopsthecurrentmatrixfromthematrixstack:

defrender(self):

glPushMatrix()

glTranslatef(*self.position)

glBegin(GL_QUADS)

glMaterialfv(GL_FRONT,GL_DIFFUSE,self.color)

forsideinCube.sides:

forvinside:

glVertex3fv(self.vertices[v])

glEnd()

glPopMatrix()

Page 215: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

EnablingfacecullingEventhoughacubehassixfaces,wecanseeamaximumofonlythreefacesatonce,andonlytwooronefromcertainangles.Therefore,ifwediscardthefacesthatarenotgoingtobevisible,wecanavoidrenderingatleast50percentofthefacesofourcubes.

Byenablingfaceculling,OpenGLcheckswhichfacesarefacingthevieweranddiscardsthefacesthatarefacingbackwards.Theonlyrequirementistodrawthefacesofthecubeinthecounterclockwiseorderofthevertices,whichisthedefaultfrontfaceinOpenGL.Theimplementationpartiseasy;weaddthefollowinglinetoourglEnablecalls:

#...

glEnable(GL_LIGHTING)

glEnable(GL_CULL_FACE)

#...

Inournextapplication,wewilladdsomecubesandenablefacecullingtoseethisoptimizationinpractice.

Page 216: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 217: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

BasiccollisiondetectiongameWithalloftheseingredients,youarenowabletowriteasimplegamethatdetectssimplecollisionsbetweenshapes.

Thesceneconsistsofaninfinitelane.Blocksappearrandomlyattheendofthelaneandmovetowardstheplayer,representedasthesphereinthefollowingscreenshot.Heorshemustavoidhittingtheblocksbymovingthespherefromrighttoleftinthehorizontalaxis.

Thegameisoverwhentheplayer’scharactercollideswithoneoftheblocks.

Thisgameplayisdirectanduncomplicated,anditwillallowustodevelopa3Dgamewithoutworryingtoomuchaboutmorecomplicatedphysicscalculations.

SincewearegoingtoreusetheLight,Cube,andSphereclasses,weneedtodefineanewclassonlytorepresentourgameblocks:

classBlock(Cube):

color=(0,0,1,1)

speed=0.01

Page 218: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

def__init__(self,position,size):

super().__init__(position,(size,1,1),Block.color)

self.size=size

defupdate(self,dt):

x,y,z=self.position

z+=Block.speed*dt

self.position=x,y,z

Itsupdate()methodsimplymovestheblocktowardstheplayerbyupdatingitszcoordinatewithuniformspeed.

OurAppclasssetstheinitialvaluesoftheattributesthatwewillneedduringtheexecutionofourgame,andcreatestheLightandthegameobjectinstancesasinourpreviousexamples:

classApp(object):

def__init__(self,width=800,height=600):

#...

self.game_over=False

self.random_dt=0

self.blocks=[]

self.light=Light(GL_LIGHT0,(0,15,-25,1))

self.player=Sphere(1,position=(0,0,0),

color=(0,1,0,1))

self.ground=Cube(position=(0,-1,-20),

size=(16,1,60),

color=(1,1,1,1))

Thestart()methodhassmallvariations,onlyaddingglEnable(GL_CULL_FACE),aswementionedpreviously:

defstart(self):

pygame.init()

#...

glMatrixMode(GL_MODELVIEW)

glEnable(GL_CULL_FACE)

self.main_loop()

Themain_loop()methodisnowaseparatemethodandincludestherandomgenerationofblocks,collisiondetection,aswellastheupdatingofthepositionsoftheblocks:

defmain_loop(self):

clock=pygame.time.Clock()

whileTrue:

foreventinpygame.event.get():

ifevent.type==QUIT:

pygame.quit()

sys.exit()

ifnotself.game_over:

self.display()

dt=clock.tick(self.fps)

forblockinself.blocks:

block.update(dt)

self.clear_past_blocks()

self.add_random_block(dt)

Page 219: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self.check_collisions()

self.process_input(dt)

Wewillimplementcollisiondetectionbycomparingtheboundariesoftheclosestblockswiththeextremesofthesphere.Sincethesphere’swidthissmallerthantheblocksize,ifoneoftheseextremesisbetweentherightandleftboundariesofablock,itwillbeconsideredasacollision:

defcheck_collisions(self):

blocks=filter(lambdax:0<x.position[2]<1,

self.blocks)

x=self.player.position[0]

r=self.player.radius

forblockinblocks:

x1=block.position[0]

s=block.size/2

ifx1-s<x-r<x1+sorx1-s<x+r<x1+s:

self.game_over=True

print("Gameover!")

Topreventthespawningoftoomanyblocks,wedefinedacountercalledrandom_dt.Itaccumulatestheelapsedtimeinmillisecondsbetweenframes,anditwilltrytospawnanewblockonlyifthesumisgreaterthan800milliseconds:

defadd_random_block(self,dt):

self.random_dt+=dt

ifself.random_dt>=800:

r=random.random()

ifr<0.1:

self.random_dt=0

self.generate_block(r)

defgenerate_block(self,r):

size=7ifr<0.03else5

offset=random.choice([-4,0,4])

self.blocks.append(Block((offset,0,-40),size))

Ifthegeneratedrandomnumberislowerthan0.1,anewblockisaddedtotheblocklistandtherandom_dtcounterisresetto0.Inthisway,theminimumelapsedtimebetweentwoblockscanbe0.8seconds,givingenoughtimetoleaveatolerabledistancefromoneblocktoanother.

Anotheroperationthatthemainloopperformsisremovingtheblocksthatarelocatedbehindthecameras’viewingarea,avoidingthecreationoftoomanyBlockinstances:

defclear_past_blocks(self):

blocks=filter(lambdax:x.position[2]>5,

self.blocks)

forblockinblocks:

self.blocks.remove(block)

delblock

Thecodefordisplayingthegameobjectsstaysassuccinctasusual,thankstothetransferofthedrawingprimitivestotherespectiverender()methods:

Page 220: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

defdisplay(self):

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

glLoadIdentity()

gluLookAt(0,10,10,

0,0,-5,

0,1,0)

self.light.render()

forblockinself.blocks:

block.render()

self.player.render()

self.ground.render()

pygame.display.flip()

Tofinishourgame,wewillmodifytheinputhandlingofourprogram.Thischangeisstraightforward,sinceweonlyneedtoupdatethexcomponentofthecharacter’spositionandtrimitsothatitcannotmoveoutofthelane:

defprocess_input(self,dt):

pressed=pygame.key.get_pressed()

x,y,z=self.player.position

ifpressed[K_LEFT]:

x-=0.01*dt

ifpressed[K_RIGHT]:

x+=0.01*dt

x=max(min(x,7),-7)

self.player.position=(x,y,z)

Inthechapter5_05.pyscript,youcanfindthefullimplementationofthegame.Runitandfeelfreetomodifyandimproveit!Youcanaddpickupitemsandkeeptrackofthescore,orgivetheplayeranumberoflivesbeforethegameisover.

Page 221: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 222: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SummaryInthischapter,youlearnedhowitispossibletoworkwithPythonandOpenGL,andwithbasicknowledgeaboutOpenGLAPIs,wewereabletodevelopasimple3Dgame.

Wesawtwocross-platformalternativesforcreatinganOpenGLcontext:GLUTandPygame.Youcandecidewhichonebettersuitsyour3Dgames,dependingonthetrade-offsofeachoption.Keepthisinmind:anadvantageofusingbothisthatyoumayadaptexistingexamplesfromonelibrarytotheother!

Withthesefoundationsof3Dcovered,inthenextchapter,wewillseehowtodevelopa3Dplatformerbasedonthesetechnologies.

Page 223: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 224: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Chapter6.PyPlatformerSofar,youhavelearnedhowtoimplementgamesinPythonwithdifferentlibraries,withtheprimaryfocusbeingonthepracticeofgamedevelopment.

Now,wewillintroducesometheoreticalconceptsthatwillnotonlycomplementthepractice,butwillalsohelpusdevelopgamesefficiently.Thistheorywillaidusinunderstandingwhygamesareconceptuallydesignedthewaytheyare.

Inthischapter,wewillcoverthesetopics:

FoundationsofthegametheoryObject-orientedprinciplesappliedtogamedevelopmentHowtoimplementasmall3DgameframeworkModularizingourfunctionalitywithreusablecomponentsAddingaphysicsenginetosimulaterigidbodies’interactionsCreatingthebuildingblocksofaplatformergame

Page 225: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AnintroductiontogamedesignThereareseveralacademicdefinitionsofwhatagameis;however,mostofthemsharethekeyterms,suchasrules,objectives,andplayers.Assumingthatallgamessharetheseconcepts,wemayasksomeinterestingquestionswhileanalyzingagame:whatisthegame’smainobjective?Whataretherulesthattheplayermustfollow?Isitdifficulttorecognizethegoalandtherulesofthesystem?

Otherdefinitionsmakereferencestoconceptssuchasresourcemanagementandinefficiencies,becausethedecisionsoftheplayerareusuallyconditionedbythelimitationsofsomeusefultokensinthegame.

Thedecisionswemakewhenwecreateagamearedeeplyrelatedwiththeseconcepts,andaswewillseelater,itisagoodexercisetothinkaboutthemevenwhenwearestartingwiththedevelopment.

Page 226: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

LeveldesignInordertosuccessfullyengageourplayers,ourgameneedstograduallyaddnewchallengesthatpreservetheirinterest.However,theseingredientsshouldbeintroducedinacoherentordersothattheplayerdoesnotbecomeconfusedbecausesheorhedoesnotknowhowtoreacttothegame’soutput.

Thismeansthattheplayerisnotonlyplayingyourgamebutalsolearninghowtoplayit.Thislearningcurveshouldbecarefullyconsideredintutorialsandthefirstlevel,becauseincorrectguidancecanleadtofrustration.

Page 227: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PlatformerskillsInourplatformergame,thefirstactionthattheplayerwilllearnishowtomovetheircharacter,whichisintuitivelyperformedbypressingthearrowkeys.Sincethereisnotanythreatnearby,theplayercanexperimentmovingaroundandcanbecomefamiliarwiththecontrolkeys.

Next,theplayerwillfaceanobstacle,andsheorheneedstolearnhowtojumpoverittocontinue.Thereisnogapbetweentheobstacleandtheground,sothereisnoriskoffallingfromtheplatformifthecharacter’sjumpistooshort,asshowninthefollowingscreenshot:

Oncethefirstobstacleiscleared,theplayerfacesacoupleofplatformswithgapsinbetween,asshowninthenextscreenshot.Nowitisrequiredtomeasurethejumpingforces,orelsethecharacterwillfallintothevoid.

Page 228: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Ifthecharacterdoesnotreachtheplatformandfalls,itwillrespawnattheinitialposition.That’sanotherlessonthattheplayerwilllearn:movewithcare,orelseyouwillhavetostartoveragain!

Onthenextplatform,theplayerwillencounteranewcharacter.Ifthecharactercollideswithit,itwillmovebacktothespawnposition.Therefore,theplayerlearnsthattheseobjectsshouldbeavoidedandtheymustjumpoverittoadvance.

Page 229: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Afterthis,theplayercanreachaplatformonwhichthereisaspinningbox.Itisplacedinthemiddleofanarrowplatform,soitisveryeasytocollidewith,asshowninthisscreenshot:

Page 230: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Whenthishappens,theplayer’sammoisincreased,sonowthecharacterisabletoshootbypressingthespacebar.Onthenextplatform,theplayercanfindanotherenemytotesttheirshootingability.Withthepickup,theplayercanshootuptofivetimes,sotheycantryoutthisabilityseveraltimesbeforerunningoutofammo.

Nowthatwehaveanalyzedtheskillsthatourplayerswillneedtolearn,wecanmoveontothearchitecturaldesignofourgame.

Page 231: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Component-basedgameenginesWhenwestartdevelopingagamefromscratch,thefirststepmaybetodefineabasicclasswithcommonattributesforallthegameobjects,suchasitspositioncoordinates,thecolor,thespeed,andsoon.ThiscouldbethebasicGameObjectorActorclasswedeclaredinpreviouschapters.

Thenyouneedtoaddothergameobjectswithmoreconcretekindsofbehavior,suchasthecharacterthatwillbecontrolledbytheplayer,ortheenemiesthatrandomlyshoottheplayablecharacter.Ifyourepresenttheseentitieswithseparateclasses,eachoneimplementsthatfunctionalitywithacustommethodinthecorrespondingclass.

Consequently,everyspecializationofanexistingentitymightbetranslatedintoanewsubclass.Supposewewanttoaddaspecialtypeofenemythatmovesalongaparticularroute,whichwewillcallPatrolEnemy.Inourclassdiagramshownhere,thisclasswillextendourEnemyclass:

Whilethisapproachmightworkforsmallgames,itbecomesmoredifficulttoscaletheorganizationoftheprojectwhentheinheritancehierarchygrows.Imaginethatwewanttoaddanothertypeofenemythatshootsonlywhenitislocatedwithinacertaindistancefromtheplayer,andathirdenemythatcombinesthisnewbehaviorwithPatrolEnemy.Thisisillustratedinthefollowingdiagram:

Page 232: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Ifsomegameobjectsfollowthebehavioroftheirancestorsandsharethefunctionalitywithotherentities,youmayneedtousemultipleinheritanceordefineintermediateclassesthatwouldbeunnecessaryotherwise.

Ontheotherhand,acomponent-baseddesignfollowstheprincipleoffavoringobjectcompositionoverclassinheritance.Thismeansthatthefunctionalityiscontainedinotherclasses,insteadofreusingitwithasubclass.

Translatedintoourexample,thisarchitectureleavesuswiththefollowingdiagram.Besides,aswewillseelater,acomponentbaseclassisalsoadded.

TheclassesundertheGameObjectsareaareGameObjectsubclasses,buttheirbehaviorisdeterminedbythecompositionoftheirComponentssubclasses.

Inthisway,wedonotintroducemultipleinheritancesolutionsandkeepthesepiecesoffunctionalityseparatedinsmallclasses,withtheadvantageofbeingabletoaddorremove

Page 233: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

themdynamically.

Inlatersections,wewillseehowthesecomponentsarealsousedtorenderobjectsinourOpenGLcontext,butnowwewillmoveontothelibrarythatwewillusetosimulatephysicsinourgame.

Page 234: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 235: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

IntroducingPymunkWewillimplementphysicsinourgamewithPymunk,a2DphysicsenginebuiltontopofChimpunk2D.

EventhoughweareusingOpenGLfor2Dgraphics,ourplatformergamewillrecreateatwo-dimensionalspace,sowewillworkontheplaneinwhichthezaxisequals0.

YoucaninstallPymunkdirectlyfrompip:

$pipinstallpymunk

TheChimpunk2DlibraryiswritteninC,soyoumightneedtocompileitonyourplatformifthedistributiondoesnotshipwiththeprecompiledlibrary.On32-bitWindowsand32-bitand64-bitLinuxversions,PymunkwillincludetheChimpunk2Dbinaries.

Forotherplatforms,orifyouwanttocompilethelibraryyourself,youcancheckouttheinstallationguideandthestepsforcompilingChimpunk2Dathttp://pymunk.readthedocs.org/en/latest/installation.html.

TipDon’treinventthewheel!

Implementingarigidbodylibraryisalaborioustask,especiallyifyouaredevelopingacasualgamefromscratch—aswearedoinginthischapter.

Fortunately,thePymunkAPIissimpleandwell-documented,soyoucangetstartedquicklyandavoidlosingtimecraftingacustomphysicsengine.

Themainclassesofthepymunkpackagearethefollowing:

Space:Atwo-dimensionalspaceinwhichthephysicssimulationwilloccur.WewilluseaSpaceinstancefortheentiregame,andwewillupdateeachframethroughitsstep()method.Body:Thisrepresentstherigidbody,thebasicunitofsimulation.Ithasapositionmember,whichwillalsorepresentthepositionofthegameobjectthatcontainstherigidbody.Theothermembersthatwewillcoverarethevelocityandforcevectors.Shape:Thebaseclassforallshapes.Wewilllimitourselvestocircleandboxshapes,definedbytheCircleandPolyclassesrespectively.Arbiter:Thisrepresentsacollisionpairbetweenshapesandisusedincollisioncallbacks.Italsocontainsinformationaboutthecontactpointsofthecollision.

Inthenextsection,wewillrelyontheseclassesandtheirattributestoholdinformationaboutgameobjectsandtheircollisions.

Page 236: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 237: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

BuildingagameframeworkTheComponentandGameObjectclassesformthecoreofourgameengine.TheinterfaceofferedbytheComponentclassisaseasyasthis:

classComponent(object):

__slots__=['gameobject']

defstart(self):

pass

defupdate(self,dt):

pass

defstop(self):

pass

defon_collide(self,other,contacts):

pass

Thesemethodsdefinethelifecycleofourcomponents:

start():Thisiscalledwhenthecomponentisaddedtothegameobjectinstance.Thecomponentkeepsareferencetothegameobjectasself.gameobject.update(dt):Thisiscalledforeachframe,wherethedtargumentistheelapsedtimeinsecondssincethepreviousframe.on_collide(other,contacts):Thisiscalledwhenthecomponent’sgameobjectcollideswithanotherrigidbody.Theotherargumentistheothergameobjectofthecollisionpair,andcontactsincludesthecontactpointsbetweentheobjects.ThislastargumentisdirectlypassedfromthePymunkAPI,andwewillseelaterhowtoworkwithit.stop():Thisiscalledwhenthecomponentisremovedfromthegameobjectinstance.

ThesewillbeinvokedfromourGameObjectclass.Aswementionedearlier,agameobjectinternallyusesarigidbodytorepresentitspositioninthexandyaxesandanextravariableforthezaxis.Bydefault,wewillworkinthez=0plane.

TheGameObjectclassisdefinedasfollows:

importpymunkaspm

classGameObject(object):

instances=[]

def__init__(self,x=0,y=0,z=0,scale=(1,1,1)):

self._body=pm.Body()

self._body.position=x,y

self._shape=None

self._ax=0

self._ay=0

self._z=z

self.tag=''

self.scale=scale

self.components=[]

GameObject.instances.append(self)

Page 238: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Thetwo-dimensionalpositioniscomplementedwithaninner_zmember,andtherotationisextendedwiththeangleofrotationinthexandyaxiswiththe_axand_aymembers,respectively.Thevelocityvectorisdirectlyretrievedfromtherigidbody:

@property

defposition(self):

pos=self._body.position

returnpos.x,pos.y,self._z

@position.setter

defposition(self,pos):

self._body.position=pos[0],pos[1]

self._z=pos[2]

@property

defrotation(self):

returnself._ax,self._ay,self._body.angle

@rotation.setter

defrotation(self,rot):

self._ax=rot[0]

self._ay=rot[1]

self._body.angle=rot[2]

@property

defvelocity(self):

returnself._body.velocity

@velocity.setter

defvelocity(self,vel):

self._body.velocity=vel

Thegameobject’spositioncanbemodifiedbyapplyinganimpulseoraforcevectortotheunderlyingrigidbody:

defmove(self,x,y):

self._body.apply_impulse((x,y))

defapply_force(self,x,y):

self._body.apply_force((x,y))

Theinstancecomponentscanbemanipulated—added,removed,andretrievedbytype—inaflexiblewaywiththefollowingmethods:

defadd_components(self,*components):

forcomponentincomponents:

self.add_component(component)

defadd_component(self,component):

self.components.append(component)

component.gameobject=self

component.start()

defget_component_by_type(self,cls):

forcomponentinself.components:

Page 239: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ifisinstance(component,cls):

returncomponent

defremove_component(self,component):

component.stop()

self.components.remove(component)

Todisplaythegameobject,wepayspecialattentiontotheRenderablecomponents.TheinterfaceofthisComponentsubclassincludearender()method,whichiscalledinthedisplayloopforeachactivegameobject.

Theupdate()methoddirectlydelegatestheexecutiontoeveryattachedcomponent:

defrender(self):

forcomponentinself.components:

ifisinstance(component,Renderable):

component.render()

defupdate(self,dt):

forcomponentinself.components:

component.update(dt)

Finally,whenweremoveagameobject,weneedtodeletetheshapeofitsrigidbodyfromthePymunkspace;otherwise,thevisualresultwillbesuchthattheentitiesstillkeepcollidingwithaninvisibleobject:

defremove(self):

forcomponentinself.components:

self.remove_component(component)

ifself._shapeisnotNone:

Physics.remove(self._shape)

GameObject.instances.remove(self)

defcollide(self,other,contacts):

forcomponentinself.components:

component.on_collide(other,contacts)

Youcanfindtheimplementationofthesetwoclassesinthepyplatformer/enginecomponents.pyandpyplatformer/engine/__init__.pyscripts.

TipSavingspaceconsumption

Bydefault,eachobjectinstancehasaninternaldictionaryforattributestorage.The__slots__classvariabletellsPythontoallocatespaceonlyforeachvariable,savingspaceiftheinstancehasfewattributes.

Sincecomponentsaresmallclasses,weaddedthisoptimizationtoavoidinstantiatingunnecessarydictionaries.

Nowwewillmoveontothephysicsmodule,wheretheInputclassandtheshapecomponentsaredefined.

Page 240: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AddingphysicsThismoduleisresponsibleforinitializingthePymunkSpace.Thishappensatthebeginningofthefile,andthegravityandthecollisionhandleraresettodefaultvalues:

importpymunk

defcoll_handler(_,arbiter):

iflen(arbiter.shapes)==2:

obj1=arbiter.shapes[0].gameobject

obj2=arbiter.shapes[1].gameobject

obj1.collide(obj2,arbiter.contacts)

obj2.collide(obj1,arbiter.contacts)

returnTrue

space=pymunk.Space()

space.gravity=0,-10

space.set_default_collision_handler(coll_handler)

ThecollisionhandlerreceivesthespacewherethecollisionoccursasthefirstparameterandanArbiterinstancewiththecollisioninformation.Here,weobtainthegameobjectattachedtotheshapeandtriggerthecollide()method.

ThePhysicsclassissimilartoInput,sinceitcontainsacoupleofclassmethodsthatwrapthebasicfunctionalitywearelookingfor:

classPhysics(object):

@classmethod

defstep(cls,dt):

space.step(dt)

@classmethod

defremove(cls,body):

space.remove(body)

TheRigidbodyclassisabasecomponentthatreplacestheinitialstaticbodythateverygameobjecthaswithanon-staticone.Italsoaddsareferencetothegameobjectfromtheshape,soitispossibletoretrievethegameobjectfromthePymunkcollisioninformation:

classRigidbody(Component):

__slots__=['mass','is_static']

def__init__(self,mass=1,is_static=True):

self.mass=mass

self.is_static=is_static

defstart(self):

ifnotself.is_static:

#Replacethestaticbody

pos=self.gameobject._body.position

body=pymunk.Body(self.mass,1666)

body.position=pos

self.gameobject._body=body

defadd_shape_to_space(self,shape):

self.gameobject._shape=shape

Page 241: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

shape.gameobject=self.gameobject

ifself.is_static:

space.add(shape)

else:

space.add(self.gameobject._body,shape)

NoteNotethatstaticbodiesarenotaddedtothespace;onlytheirshapesare.

TheBoxColliderandSphereCollidersubclassescreatethecorrespondingshapebycallingthePymunkAPI:

classBoxCollider(Rigidbody):

__slots__=['size']

def__init__(self,width,height,mass=1,is_static=True):

super(BoxCollider,self).__init__(mass,is_static)

self.size=width,height

defstart(self):

super(BoxCollider,self).start()

body=self.gameobject._body

shape=pymunk.Poly.create_box(body,self.size)

self.add_shape_to_space(shape)

classSphereCollider(Rigidbody):

__slots__=['radius']

def__init__(self,radius,mass=1,is_static=True):

super(SphereCollider,self).__init__(mass,is_static)

self.radius=radius

defstart(self):

super(SphereCollider,self).start()

body=self.gameobject._body

shape=pymunk.Circle(body,self.radius)

self.add_shape_to_space(shape)

Eachimplementationonlydiffersintheinformationthatispassedtobuildtheshape,butyoucaneasilyaddanotherrigidbodycomponentbyfollowingthesamepattern.

Page 242: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

RenderablecomponentsThecomponentsmoduleincludesthedefinitionoftheRenderableclass,whichwementionedearlierwhenwelookedathowtodisplaygameobjects.

Itpushesamatrixtothecurrentstackandperformssomebasicoperationsbeforedelegatingtherenderingtothe_render()method,savingusfromrepeatingthisboilerplatecode:

classRenderable(Component):

__slots__=['color']

def__init__(self,color):

self.color=color

defrender(self):

pos=self.gameobject.position

rot=self.gameobject.rotation

scale=self.gameobject.scale

glPushMatrix()

glTranslatef(*pos)

ifrot!=(0,0,0):

glRotatef(rot[0],1,0,0)

glRotatef(rot[1],0,1,0)

glRotatef(rot[2],0,0,1)

ifscale!=(1,1,1):

glScalef(*scale)

ifself.colorisnotNone:

glColor4f(*self.color)

self._render()

glPopMatrix()

def_render(self):

pass

TheCubeandSpheresubclassesareanadaptationoftheimplementationwecoveredinChapter5,PyGameand3D,andalongwiththeLightclass,theyhavebeenomittedforbrevity.

TheCameracomponentYoumayrememberhowtosetupthecameraperspectivewithGLU,whichwasoneofthefirstactionsofthedisplayloopinthepreviouschapter.

Nowwewilluseacomponenttoperformthesametask,butitwilldifferfromothercomponentsinthewayinwhichitiscalledinthemainloop.ThishappensbecausethesematrixoperationsneedtobethefirstonesoftheOpenGLmatrixstackifwewanttosetupthecamerapositioncorrectly:

classCamera(Component):

instance=None

def__init__(self,dy,dz):

self.dy=dy

Page 243: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self.dz=dz

Camera.instance=self

defrender(self):

pos=self.gameobject.position

glLoadIdentity()

gluLookAt(pos[0],self.dy,self.dz,

pos[0],pos[1],pos[2],

0,1,0)

Thecamerawilllookatthegameobjectwithwhichitisattachedandthedistancealongtheyandzaxesareparameterizedinits__init__method.

Thisimplementationtakesthelastcamerathathasbeeninstantiatedasthecurrentone.Inourgame,wewillonlyuseasinglecameracomponent,andthiscanbeconsideredacommonscenarioinbasicgames.

However,ifyouneedtoswitchbetweenmultiplecameras,youcandefineamorecomplexcomponent.Asyoumaynotice,theseimplementationsareverycleanandconcisethankstotheassumptionofthesesimplifications.

Page 244: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TheInputManagermoduleTohandleinputevents,wecreateaseparatemodulethatwillwraptheprocessingofourpygameevents.SincewewillregisterkeystrokesandthespecialQUITeventonly,thisclassisquiteuncomplicated:

fromcollectionsimportdefaultdict

importpygame

classInput(object):

quit_flag=False

keys=defaultdict(bool)

keys_down=defaultdict(bool)

@classmethod

defupdate(cls):

cls.keys_down.clear()

foreventinpygame.event.get():

ifevent.type==pygame.QUIT:

cls.quit_flag=True

ifevent.type==pygame.KEYUP:

cls.keys[event.key]=False

ifevent.type==pygame.KEYDOWN:

cls.keys[event.key]=True

cls.keys_down[event.key]=True

@classmethod

defget_key(cls,key):

returncls.keys[key]

@classmethod

defget_key_down(cls,key):

returncls.keys_down[key]

Notethatwiththisinterface,itisstraightforwardtogetthepressedkeyswithinanycomponent,likethisforinstance:

classHorizontalMovement(Component):

defupdate(self,dt):

direction=Input.get(K_RIGHT)-Input.get(K_RIGHT)

self.gameobject.move(dt*direction*5,0)

Wecansubtractbooleanvaluesbecauseboolisasubclassofint,andTruecorrespondsto1,whileFalsecorrespondsto0.

Page 245: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TheGameclassTheGameclassisresponsibleforbootstrappingtheapplication—settingtheinitialvaluesofthewindowattributes,initializingtheOpenGLcontext,andenteringthegameloop:

classGame(object):

def__init__(self,caption,width=800,height=600):

self.caption=caption

self.width=width

self.height=height

self.fps=60

defmainloop(self):

self.setup()

clock=pygame.time.Clock()

whilenotInput.quit_flag:

dt=clock.tick(self.fps)

dt/=1000

Physics.step(dt)

self.update(dt)

self.render()

pygame.quit()

sys.exit()

Someoftheseinitialoperationshavebeenextractedintothesetup()method,somainloop()staysclearandunderstandable:

defsetup(self):

pygame.init()

pygame.display.set_mode((self.width,self.height),

pygame.OPENGL|pygame.DOUBLEBUF)

pygame.display.set_caption(self.caption)

glEnable(GL_LIGHTING)

glEnable(GL_COLOR_MATERIAL)

glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE)

glEnable(GL_DEPTH_TEST)

glClearColor(0.5,0.7,1,1)

glMatrixMode(GL_PROJECTION)

aspect=self.width/self.height

gluPerspective(45,aspect,1,100)

glMatrixMode(GL_MODELVIEW)

Theupdate()andrender()callsdelegatetheexecutiontothegameobjectinstances,whichinturn,willinvoketheircomponents’methods:

defupdate(self,dt):

Input.update()

forgameobjectinGameObject.instances:

gameobject.update(dt)

defrender(self):

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

ifCamera.instanceisnotNone:

Camera.instance.render()

forgameobjectinGameObject.instances:

Page 246: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

gameobject.render()

pygame.display.flip()

Thefinalarrangementofourenginepackagelookslikethis:

Thepyplatformer/game.pyscriptcontainsthegamelogic,andthankstothemicro-frameworkthatwehavedeveloped,itwillconsistofadozenshortclassesonly.

Page 247: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 248: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

DevelopingPyPlatformerWiththisarchitecturalbackground,wecanstartcraftingthecustomcomponentsofourplatformergame.TheComponentAPImaylooksimple,butitallowsustoimplementthefunctionalityinasuccinctmanner.

Page 249: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CreatingtheplatformsEachplatformofourgameisnothingbutacubewithastaticrigidbody.However,sincewewillcreateseveralinstanceswiththesecomponents,itisconvenienttodefineaclasstoavoidrepeatingthiskindofinstantiation:

classPlatform(GameObject):

def__init__(self,x,y,width,height):

super(Platform,self).__init__(x,y)

color=(0.2,1,0.5,1)

self.add_components(Cube(color,size=(width,height,2)),

BoxCollider(width,height))

Page 250: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AddingpickupsInplatformergames,itiscommonthattheplayerisabletocollectsomeitemsthatgivevaluableresources.Inourgame,whenthecharacterhitsoneofthesepickups,itsammoisincrementedbyfiveunits.

Todecoratethistypeofgameobject,wewilladdacomponentthatrotatestheattachedgameobjectarounditsyaxis:

classRotating(Component):

speed=50

defupdate(self,dt):

ax,ay,az=self.gameobject.rotation

ay=(ay+self.speed*dt)%360

self.gameobject.rotation=ax,ay,az

Finally,wecandefineaGameObjectsubclassthatwrapstheinstantiationofthesecomponents:

classPickup(GameObject):

def__init__(self,x,y):

super(Pickup,self).__init__(x,y)

self.tag='pickup'

color=(1,1,0.5,1)

self.add_components(Cube(color,size=(1,1,1)),

Rotating(),BoxCollider(1,1))

Weusethetagattributeasawayofidentifyingitstype.Thus,ifweneedtocheckthegameobject’stype,wedon’tneedtorelyontheisinstancefunctionandthehierarchymodel.

Page 251: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Shooting!Whentheplayercollideswithoneofthesepickups,itisdestroyedanditisnolongerdisplayedonthescene.Withaphysicsengine,thisalsomeansremovingthegameobjectanddisablingitsrigidbody.RememberthatwedefinedtheGameObject.remove()methodforthispurpose.

Tosimulatethegradualdisappearanceoftheobject,wewilldecreaseitsscaleuntilitdoesnotbecomevisible,andthenwewillremoveit:

classDisappear(Component):

defupdate(self,dt):

self.gameobject.velocity=0,0

s1,s2,s3=map(lambdas:s-dt*2,

self.gameobject.scale)

self.gameobject.scale=s1,s2,s3

ifs1<=0:self.gameobject.remove()

Thiscomponentisattacheddynamicallytothepickupandforcesitsremoval.TheabilitytoshootandcollecttheseitemsisdefinedintheShootercomponent:

classShoot(Component):

defon_collide(self,other,contacts):

self.gameobject.remove()

classShooter(Component):

__slots__=['ammo']

def__init__(self):

self.ammo=0

defupdate(self,dt):

ifInput.get_key_down(K_SPACE)andself.ammo>0:

self.ammo-=1

d=1ifself.gameobject.velocity.x>0else-1

pos=self.gameobject.position

shoot=GameObject(pos[0]+1.5*d,pos[1])

shoot.tag='shoot'

color=(1,1,0,1)

shoot.add_components(Sphere(0.3,color),Shoot(),

SphereCollider(0.3,mass=0.1,

is_static=False))

shoot.apply_force(20*direction,0)

defon_collide(self,other,contacts):

ifother.tag=='pickup':

self.ammo+=5

other.add_component(Disappear())

Eachtimetheplayershoots,agameobjectisinstantiatedandmovestowardsthecurrentgameobject’sdirection.IthasanauxiliaryShootcomponent.Thiscomponentremovestheshootinstancewhenitcollideswithotherrigidbodies.

Todecidewhichentitiesareaffectedbyaplayer’sshooting,weaddacomponentthat

Page 252: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

checksthecollidingentity’staganddisappearsifitisashoot:

classShootable(Component):

defon_collide(self,other,contacts):

ifother.tag=='shoot':

self.gameobject.add_component(Disappear())

Noweachenemyhasthefollowingcomponents:

classEnemy(GameObject):

def__init__(self,x,y):

super(Enemy,self).__init__(x,y)

self.tag='enemy'

color=(1,0.2,0.2,1)

self.add_components(Sphere(1,color),Shootable(),

SphereCollider(1,is_static=False))

Page 253: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThePlayerclassanditscomponentsApartfromtherigidbodyandtheShooter,ourplayercharacterhastwomaincomponents:

Respawn:Thisspawnstheplayerinitsinitialpositionifitfallsintothevoidorcollideswithanenemy.ThefollowingisanexampleoftheRespawnclass:

classRespawn(Component):

__slots__=['limit','spawn_position']

def__init__(self,limit=-15):

self.limit=limit

self.spawn_position=None

defstart(self):

self.spawn_position=self.gameobject.position

defupdate(self,dt):

ifself.gameobject.position[1]<self.limit:

self.respawn()

defon_collide(self,other,contacts):

ifother.tag=='enemy':

self.respawn()

defrespawn(self):

self.gameobject.velocity=0,0

self.gameobject.position=self.spawn_position

PlayerMovement:Thisqueriestheinputstateandcheckswhatforcescanbeappliedtothecharacter’srigidbodytomoveithorizontallyormakeitjump.HereisanexampleofthePlayerMovementclass:

classPlayerMovement(Component):

__slots__=['can_jump']

def__init__(self):

self.can_jump=False

defupdate(self,dt):

d=Input.get_key(K_RIGHT)-Input.get_key(K_LEFT)

self.gameobject.move(d*5*dt,0)

ifInput.get_key(K_UP)andself.can_jump:

self.can_jump=False

self.gameobject.move(0,8)

defon_collide(self,other,contacts):

self.can_jump|=any(c.normal.y<0forcincontacts)

NoteNotehowweusethelistofcontactpointstocheckwhethertheplayerhastouchedtheground,andthenenablethejumpmovementagain.

Page 254: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThePlayerclasscombinesallofthesecomponentsintoasingleentity:

classPlayer(GameObject):

def__init__(self,x,y):

super(Player,self).__init__(x,y)

self.add_components(Sphere(1,(1,1,1,1)),

PlayerMovement(),Respawn(),

Shooter(),Camera(10,20),

SphereCollider(1,is_static=False))

Page 255: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ThePyPlatformerclassFinally,wedefineaGamesubclassthatinstantiatesallofourgameobjectsandplacestheminthescene:

classPyPlatformer(Game):

def__init__(self):

super(PyPlatformer,self).__init__('PyPlatformer')

self.player=Player(-2,0)

self.light=GameObject(0,10,0)

self.light.add_component(Light(GL_LIGHT0))

self.ground=[

#Platform1

Platform(3,-2,30,1),

Platform(-11,3,2,9),

Platform(8,0,2,3),

#Platform2&3

Platform(23,0,6,1),

Platform(40,2,24,1),

#Platform4&5

Platform(60,3,8,1),

Platform(84,4,26,1)

]

self.pickup=Pickup(60,5)

self.enemies=[Enemy(40,4),Enemy(90,6)]

if__name__=='__main__':

game=PyPlatformer()

game.mainloop()

Youcancheckoutallthegameobjectsandcomponentsinthepyplatformer/game.pyscript.

Page 256: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 257: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SummaryInthischapter,youlearnedaboutthebenefitsofacomponent-baseddesignandhowitallowsyoutobuildsmallpiecesthatcanbeadded,removed,andcombinedwithseveralgameobjects.

Notethatthemostimportantexerciseofthischapterisnothowtoimplementaplatformergame,buthowitispossibletosetupacomponent-basedframeworkandprovidethebasicbuildingblocksofagame.

Withthesefoundations,youcanaddsomebasicfunctionality,suchasmovingtheenemiesandtheplatforms,displayingmoreinformationabouttheammo,orkeepingtrackofascorebasedonthenumberofliveslostandenemiesdestroyed.

Asusual,thefinalversionofthischapter’sgameisnothingbutthestartingpointofamorecomplexapplication!

Inthenextchapter,wewillinteractwithareal-wordcheckersgameviaawebcam.ThisapplicationwillbedevelopedwithOpenCV,across-platformcomputervisionlibrary.

Page 258: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 259: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Chapter7.AugmentingaBoardGamewithComputerVisionComputervisionisthescienceandengineeringofsmartcamerasystems(ormorebroadly,smartimagesystems,sinceimagescancomefromanothersourcebesidesacamera).Examplesofsubtopicsincomputervisionincludefacerecognition,licenseplaterecognition,imageclassification(asusedinGoogle’sSearchbyimage),motioncapture(asusedinXboxKinectgames),and3Dscanning.

Computervision,likegamedevelopment,hasbecomemoreaccessibleinrecentyearsandisnowalmostaubiquitoustopic.“Howcanweleveragepeople’sinterestincameras?”or“Howcanweleverageallthecamerasthatareinourbuilding,ourcity,orourcountry?”isasnaturalaquestionas“Howcanweleveragepeople’sinterestingamesandsimulations?”andtheimplicationsextendbeyondentertainment.

Camerasareeverywhere.Manyofthemareattachedtopowerfulhostcomputersandnetworks.Welive,work,andplayamidstanarmyofdigitaleyes,includingwebcams,cameraphones,camera-controlledgameconsolesandsmartTVs,securitycameras,drones,andsatellites.Imagesofyoumaybecaptured,processed,ortransmittedmanytimesdaily.

ThesongwriterJohnLennonaskedusto“imagineallthepeoplelivingfortoday”but,foramoment,let’simagineallpixelsinstead.Asingleimagemaycontainmillionsofpixels,amountingtomorebytesofdatathanLeoTolstoy’sWarandPeace(anepic1500-pagenovelaboutRussiansocietyduringtheNapoleonicWars).Asinglecameramaycaptureavideostreamcontainingthousandsoftheseepic-sizedimagesperminute.Billionsofcamerasareactiveintheworld.Networksanddiskdrivesarecongestedwiththeaccumulationofimagedata,andonceanimageisonline,copiesofitmayremainincirculationindefinitely.Whenweusesoftwaretoacquire,edit,analyze,andreviewstreamsofimages,wemaynoticethatthecomputer’sCPUusagesoarswhileitsbatterypowerplummets.

Asgamedevelopers,weknowthatagoodgameenginesimplifiesalotofoptimizationproblems,suchasbatchingspritesor3DmodelstosendtotheGPUforrendering.Goodcomputervisionlibraries(andmorebroadly,goodnumericandscientificlibraries)alsosimplifyalotofoptimizationproblemsandhelpusconserveCPUusageandbatterylifewhileprocessingvideoinput(orotherlargestreamsofdata)inrealtime.Sincethe1990s,computervisionlibraries,likegameengines,havebecomemorenumerous,faster,easiertouse,andoftenfree.Thischapterleveragesthefollowinglibrariestocapture,process,anddisplayimages:

NumPy:Thisisanumericlibrary,whichwepreviouslyusedinChapter4,SteeringBehaviors.Itsdocumentationisathttp://docs.scipy.org/doc/.OpenCV:Thisisacross-platformlibraryforcomputationalphotography,computervision,andmachinelearning.OpenCV’sPythonversionrepresentsimagesasNumPyarrays.However,beneaththePythonlayer,OpenCVisimplementedinC++code

Page 260: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

withawiderangeofhardware-specificoptimizations.Thankstotheseoptimizations,OpenCVfunctionstendtorunfasterthantheirNumPyequivalents.TheOpenCVdocumentationisathttp://docs.opencv.org/.scikit-learn:Thisisamachinelearninglibrary.ItprocessesNumPyarrays.Foritsdocumentation,refertohttp://scikit-learn.org/stable/documentation.html.WxPython:Thisisacross-platformGUIframework.Oneachplatform,WxPythoniscertaintohaveanativelookandfeelbecauseitusesnativeGUIwidgets(incomparison,manycross-platformGUIframeworks,suchasTkinter,usetheirownnon-nativewidgetswithaconfigurable“skin”thatmayemulateanativelook).WxPythonisawrapperaroundaC++librarycalledWxWidgets.ForWxPython’sdocumentation,refertohttp://wxpython.org/onlinedocs.php.

NoteLaterinthischapter,intheSettingupOpenCVandotherdependenciessection,wewilldiscusstheversionrequirementsandsetupstepsforeachlibrary.

Followingthepatternofotherchaptersinthisbook,wewillapplycomputervisiontoaclassicgame:checkers,alsoknownasdraughts.Thisboardgamehasmanyvariantsfromallculturesandallperiodsofhistoryinthepast5,000years.Itisthegrandfatherofallstrategygames.Acrossmostvariants,thegamehasthefollowingfeatures:

Therearetwoplayers,whoaresometimescalledlightanddark.Therearetwokindsofplayingpieces,sometimescalledpawnsandkings.Apawnisashort,circularplayingpiece.Akingisatall,circularplayingpiecemadebystackingtwopawns.Theboardisagridofalternatinglightanddarksquares.Piecesmayonlyoccupythedarksquares.Thesizeofthegriddependsonthevariantofthegame.Atthestartofthegame,eachplayerhasanarmyofpawns,occupyingmultipleadjacentrowsononesideoftheboard.Thetwoarmiesstartonoppositesidesoftheboardwithtwovacantrowsbetweenthem.Apiecemaycaptureanopposingpiecebyjumpingoveritintoanunoccupiedsquare.Apiecemaymakemultiplejumpsinoneturn.Onreachingthefarthestrow,apawnispromotedtoaking.Thedifferencebetweenapawnandakingdependsonthevariant.Insomevariants,onlyakingcanmovebacktowarditsstartingside.Moreover,insomevariants,apawncancrossonlyoneunoccupiedsquareatatimewhileakingcancrossmultipleunoccupiedsquares.Thelatterarecalledflyingkings.

NoteFordescriptionsofmanyinternationalvariantsofcheckers,ordraughts,refertohttps://en.wikipedia.org/wiki/Draughts.

Wewillbuildanapplicationthatmonitorsareal-worldgameofcheckersviaawebcam.Theapplicationwilldetectacheckerboardandclassifyeachsquareasanemptysquare,alightpawn,alightking,adarkpawn,oradarkking.Furthermore,theapplicationwillalterthewebcam’simagestocreateabird’s-eyeviewoftheboard,withlabelstoshowthe

Page 261: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

resultsoftheclassification.Thisisasimplecaseofaugmentedreality,meaningthattheapplicationappliesspecialeffectsandannotationstoareal-timeviewofareal-worldobject.Wewillcallthisapplication,quitesimply,Checkers.

NoteThecompletedprojectforthischaptercanbedownloadedfromhttp://nummist.com/opencv/4507_07.zip,andanyFAQanderratacanbefoundathttp://nummist.com/opencv.

Page 262: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

PlanningtheCheckersapplicationLet’sgivefurtherthoughttothereal-worldscenethatourCheckersprogramwillexpectandthevirtualsceneitwillcreate.Considerthefollowingclose-upphotograph,showingpartofan8x8checkerboard.Fromthetop-leftcornertoright-handside,weseealightking,alightpawn,adarkpawn,andadarkking.

Thischeckerboardisjustasheetofmattepaperonwhichblackandwhitesquaresareprinted(thepaperisgluedtoafoamboardtomakeitrigid).Theplayingpieceshappentobepokerchips—redchipsforthedarksideandgraychipsforthelightside.Astackoftwopokerchipsisapawn,whileastackoffourisaking.Asthisexamplesuggests,peoplemayplaycheckerswithhomemadeorimprovisedsets.Thereisnoguaranteethattwocheckerssetswilllookalike,sowewilltrytoavoidrigidassumptionsaboutthecolorscheme.However,highcontrastisgenerallyhelpfulincomputervision,andthisexampleisidealbecauseitusesfourcontrastingcolorsfordarksquares,lightsquares,darkpieces,andlightpieces.

Wewillassumethatalightbordersurroundsthecheckerboard(seetheprecedingimage).Thisassumptionmakesiteasiertodetecttheboard’sedge.

Lookcarefullyatthewhitesquarestotheleftofthekings.Sincethekingsaretallerthanthepawns,thekingscastlongershadowsintoadjacentlightsquares.Wewillrelyonthisobservationtodifferentiatebetweenpawnsandkings.Importantly,inabird’s-eyeviewoftheboard,theheightsoftheplayingpieceswillnotbevisiblebuttheshadowswillbe.Wewillassumethattheshadowsareapproximatelyorthogonal(notdiagonal),andaresufficientlylongtoreachanadjacentsquare.

Tofurthersimplifyourcomputervisionwork,wewillrequirethatthecameraandcheckerboardremainstationaryrelativetoeachother.Thecamerashouldhaveaviewoftheentireboardplusasmallmargin.Toachievethis,thewebcamwillneedtobeapproximately2feet,or0.6meters,awayfromtheboard(however,theexactrequirementwillvarydependingonthewebcam’sfieldofviewandtheboard’ssize).Asseeninthe

Page 263: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

followingphotograph,atripodisagoodwaytokeepthewebcamstationaryinahighpositionfromwhereitcanviewthewholeboard:

Asthefollowingscreenshotshows,ourCheckersapplicationwilldisplayboththeunmodifiedcameraview(left)andanidealizedbird’s-eyeview(right):

Page 264: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Thebird’s-eyeviewcontainsannotations,orinotherwords,textforshowingtheclassificationresults.Theannotation1meansadarkpawn,11adarkking,2alightpawn,and22alightking.Alackofannotationmeansanemptysquare.

NotetheGUIwidgetsatthebottomofthescreenshot.Thesecontrolswillenabletheusertoadjustcertainparametersofourcheckerboarddetectorandsquareclassifier.Specifically,thefollowingadjustmentswillbesupported:

RotateboardCCW:Clickonthisbuttontorotatethebird’s-eyeview90degreescounterclockwise(CCW)FlipboardX:Clickonthisbuttontoflipthebird’s-eyeviewhorizontallyFlipboardY:Clickonthisbuttontoflipthebird’s-eyeviewverticallyShadowdirection:Selectoneoftheradiobuttons—up,left,down,orright—tospecifythedirectionofthekings’shadowsinthebird’s-eyeviewEmptythreshold:Movethisslidertothelefttoclassifymoresquaresasnonempty,ortotherighttoclassifymoresquaresasemptyPlayerthreshold:Movethisslidertothelefttoclassifymorepiecesaslight,ortotherighttoclassifymorepiecesasdarkShadowthreshold:Movethisslidertothelefttoclassifymorepiecesaskings,ortotherighttoclassifymorepiecesaspawns

WewilldivideourimplementationoftheCheckersapplicationintosixPythonmodules:

Checkers.py:ThismoduleimplementstheGUIusingWxPython.CheckersModel.py:ThismoduleusesOpenCV,NumPy,andscikit-learntocapture,analyze,andaugmentimagesofacheckersgameinrealtime.WxUtils.py:ThismoduleprovidesautilityfunctiontoconvertimagesfromOpenCVformatstoadisplayableWxPythonformat.TheimplementationincludesaworkaroundforaWxPythonbugthataffectsfirst-generationRaspberryPicomputers.ResizeUtils.py:UsingOpenCV,thismoduleprovidesautilityfunctiontotrytosetacamera’sresolutionandreturntheactualresolution.ColorUtils.py:UsingNumPy,thismoduleprovidesutilityfunctionstoextractacolorchannel(suchastheredcomponentofanimage)andquantifydifferencesthebetweencolors.CVBackwardCompat.py:ThismoduleprovidesaliasessothatthingsinOpenCV2.xappeartohavethesamenamesastheirequivalentsinOpenCV3.x.

Beforewewriteanyofthesemodules,let’ssetupthedependencies.

Page 265: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 266: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SettingupOpenCVandotherdependenciesThischapter’sprojectwillworkwithOpenCV2.xor3.xandPython2.7or3.4.NotethatOpenCV2.xdoesnotsupportPython3.x.However,onmostLinuxsystems,itismoreconvenienttoinstallOpenCV2.x(andusePython2.7)becauseOpenCV3.xdoesnothavebinarypackagesformostLinuxsystemsyet.

ThefollowingsubsectionscoveronlythesimplestwaystosetupourdependenciesonWindows,Mac,andseveralLinuxdistributions.Otherapproaches(andotherUnix-likeplatforms)canalsowork.Forexample,advancedusersmaywishtoconfigureandbuildOpenCV3.0’ssourcecodeonLinuxsystemsthatdonotyethaveprepackagedbuildsofOpenCV3.x.Forguidanceonalternativesetups,refertooneofPacktPublishing’sdedicatedbooksonOpenCV,suchasLearningOpenCV3ComputerVisionwithPythonbyJoeMinichinoandJosephHowse.

Page 267: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

WindowsChristophGohlke,fromtheUniversityofCalifornia,Irvine,providesmanyreliableprebuiltversionsofscientificPythonlibrariesforWindows.Gotohisdownloadsiteathttp://www.lfd.uci.edu/~gohlke/pythonlibs/.IfyouhadnotinstalledNumPyinChapter4,Steeringbehaviors,getthelatestNumPy+MKLwhlfilefromGohlke’ssite(MKLisanIntelMathKernelLibrarythatprovidesoptimizations).Atthetimeofwritingthisbook,itisoneofthefollowing:

Python2.7,32-bit:numpy‑1.9.2+mkl‑cp27‑none‑win32.whlPython2.7,64-bit:numpy‑1.9.2+mkl‑cp27‑none‑win_amd64.whlPython3.4,32-bit:numpy‑1.9.2+mkl‑cp34‑none‑win32.whlPython3.4,64-bit:numpy‑1.9.2+mkl‑cp34‑none‑win_amd64.whl

OpenCommandPromptandusepiptoinstallNumPyfromthewheelfile.Therelevantcommandwillbesomethinglikethis:

$pipinstallnumpy‑1.9.2+mkl‑cp27‑none‑win32.whl

TheoutputfromthiscommandshouldincludeSuccessfullyinstallednumpy-1.9.2+mklorasimilarline.

NoteIfyourunintoproblemslaterwhileimportingmodulesfromOpenCVorscikit-learn,tryuninstallinganypreviousversionofNumPyfromChapter4,SteeringbehaviorsandusingthelatestNumPy+MKLwhlfilefromGohlke’ssite.

Similarly,getGohlke’slatestwhlfileforscikit-learnandinstallitwithpip.Atthetimeofwritingthisbook,itisoneofthefollowing:

Python2.7,32-bit:scikit_learn‑0.16.1‑cp27‑none‑win32.whlPython2.7,64-bit:scikit_learn‑0.16.1‑cp27‑none‑win_amd64.whlPython3.4,32-bit:scikit_learn‑0.16.1‑cp34‑none‑win32.whlPython3.4,64-bit:scikit_learn‑0.16.1‑cp34‑none‑win_amd64.whl

Also,atthetimeofwritingthisbook,GohlkeoffersdownloadsofOpenCV2.4forPython2.7andOpenCV3.0forPython3.4.Thefollowingarethelatestversions:

Python2.7,32-bit:opencv_python‑2.4.11‑cp27‑none‑win32.whlPython2.7,64-bit:opencv_python‑2.4.11‑cp27‑none‑win_amd64.whlPython3.4,32-bit:opencv_python‑3.0.0‑cp34‑none‑win32.whlPython3.4,64-bit:opencv_python‑3.0.0‑cp34‑none‑win_amd64.whl

IfyouaresatisfiedwithusingGohlke’slatestbuildofOpenCV,downloadthewhlfileandinstallitwithpip.

Alternatively,togetOpenCV3.xwithPython2.7bindings,wecandownloadanofficialbuildfromtheOpenCVsiteandperformsomeinstallationstepsmanually.TheavailabledownloadsofOpenCVarelistedathttp://opencv.org/downloads.html.GetthelatestversionforWindows.Atthetimeofwritingthisbook,thelatestfileiscalledopencv-

Page 268: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

3.0.0.exe,anditincludesprebuiltbindingsforPython2.7butnot3.4.Runit,andwhenprompted,enterthefolderintowhichyouwanttoextractOpenCV.Wewillrefertothislocationas<opencv_unzip_path>.Amongotherthings,theextractedsubfolderscontainthepydanddllfiles,whicharecompiledlibraryfilesthatthePythoninterpretercanloadatruntime.Next,wemustensurethatPythoncanfindthesefiles.

CopyoneofthefollowingpydfilestothePythonsite-packagesfolder:

Python2.7,32-bit:<opencv_unzip_path>/opencv/build/python/x86/cv2.pydPython2.7,64-bit:<opencv_unzip_path>/opencv/build/python/x64/cv2.pyd

NoteTypically,thePython2.7site-packagesfolderwouldbelocatedatC:\Python27\Lib\site-packages.

Now,editthesystem’sPathvariabletoincludeoneofthefollowingfolders(whichcontaindllfilesofOpenCVandsomeofitsdependencies):

Python2.7,32-bit:<opencv_unzip_path>/build/x86/vc12/binPython2.7,64-bit:<opencv_unzip_path>/build/x64/vc12/bin

NoteThePathvariablecanbeeditedbygoingtoControlPanel|SystemandSecurity|System|Advancedsystemsettings|EnvironmentVariables….EdittheexistingvalueofPathbyappendingsomethinglike;<opencv_unzip_path>/build/x86/vc12/bin.Notetheuseofthesemicolonasaseparatorbetweenpaths.

Finally,werequirewxPython.ForPython2.7,wxPythoninstallersareavailableathttp://wxpython.org/download.php.Downloadandrunthelatestinstaller.Atthetimeofwriting,itisoneofthefollowingexefiles:

Python2.7,32-bit:wxPython3.0-win32-3.0.2.0-py27.exePython2.7,64-bit:wxPython3.0-win64-3.0.2.0-py27.exe

ForPython3.4,wemustusewxPythonPhoenix,whichisamoreactivelydevelopedforkofwxPython.SnapshotbuildsofwxPythonPhoenixarelistedathttp://wxpython.org/Phoenix/snapshot-builds/.DownloadthelatestwhlfileforPython3.4onWindows,andinstallitwithpip.Atthetimeofwritingthisbook,itisoneofthefollowing:

Python3.4,32-bit:wxPython_Phoenix-3.0.3.dev1764+9289a7c-cp34-none-win32.whl

Python3.4,64-bit:wxPython_Phoenix-3.0.3.dev1764+9289a7c-cp34-none-win_amd64.whl

Now,wehaveallthenecessarydependenciesfordevelopingourprojectonWindows.

Page 269: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

MacTheMacPortspackagemanagerprovidesaneasycommand-linetooltoconfigure,build,andinstallopensourcesoftware,suchasOpenCV.MacPortsitselfisanopensourcecommunityproject,butitdependsonApple’sXcodedevelopmentenvironment,includingtheXcodeCommandLineTools.TosetupXcode,theXcodeCommandLineTools,andthenMacPorts,followtheinstructionsgivenathttps://www.macports.org/install.php.

OnceMacPortsissetup,wecanopentheterminalandstartinstallingpackages,alsoknownasports.Atypicalinstallationcommandhasthefollowingformat:

$sudoportinstall<package>+<variant_0>+<variant_1>...

Thevariantsarealternativeconfigurationsoftheport.Forexample,OpenCVcanbeconfiguredtobuilditsPythonbindingsandtouseoptimizationsforcertainpiecesofhardware.TheseoptimizationsaddmoredependenciestoOpenCVbutmaymakeitsfunctionsrunfaster.WewilltellOpenCVtooptimizeitselfviathreeframeworks:Eigen(forCPUvectorprocessing),IntelThreadBuildingBlocks(TBB,forCPUmultiprocessing),andOpenCL(forGPUmultiprocessing).TheOpenCLoptimizationsarenotusedbythePythoninterface,butitisgoodtohavethemjustincaseyouworkinfuturewithOpenCVinotherlanguages.

TipTosearchforportsbyname,runacommandsuchasthefollowing:

$portlist*cv*

Theprecedingcommandwilllistallportswhosenamescontaincv,notablytheopencvport.Tolistthevariantsofaport,runacommandsuchasthis:

$portvariantsopencv

ToinstallOpenCVforPython2.7,runthefollowingcommandintheterminal:

$sudoportinstallopencv+python27+eigen+tbb+opencl

Alternatively,toinstallitforPython3.4,runthiscommand:

$sudoportinstallopencv+python34+eigen+tbb+opencl

Notethatopencv+python27dependsonpython27andpy27-numpy,whileopencv+python34dependsonpython34andpy34-numpy.Thus,therelevantPythonversionandNumPyversionarealsoinstalled.AllMacPortsPythoninstallationsareseparatefromMac’sbuilt-inPythoninstallation(sometimescalledApplePython).TousetheMacPortsPython2.7installationasyourdefaultPythoninterpreter,runthefollowingcommand:

$sudoportselectpythonpython27

Alternatively,tomaketheMacPortsPython3.4installationyourdefaultPythoninterpreter,runthiscommand:

$sudoportselectpythonpython34

Page 270: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

IfiteverbecomesnecessarytomakeApplePythonthedefaultagain,runacommandsuchasthefollowing:

$portselectpythonpython27-apple

ForPython2.7,hereisacommandthatservestoinstallscikit-learn:

$sudoportinstallpy27-scikit-learn

Similarly,forPython3.4,thefollowingcommandinstallsscikit-learn:

$sudoportinstallpy34-scikit-learn

ToinstallwxPythonforPython2.7,runthiscommand:

$sudoportinstallpy27-wxpython-3.0

ForPython3.x,weneedtousewxPythonPhoenix,whichisamoreactivelydevelopedforkofwxPython.Atthetimeofwritingthisbook,MacPortsdoesnotcontainanywxPythonPhoenixpackages,sowewillfirstsetuppipandthenuseittoinstallthelatestsnapshotbuildofwxPythonPhoenix.ToinstallpipforPython3.4,runthefollowingcommand:

$sudoportinstallpy34-pip

Wecanmakethenewlyinstalledpipthedefaultpipexecutable:

$sudoportselectpippip34

SnapshotbuildsofwxPythonPhoenixarelistedathttp://wxpython.org/Phoenix/snapshot-builds/.DownloadthelatestwhlfileforPython3.4onMac.Atthetimeofwritingthisbook,itiswxPython_Phoenix-3.0.3.dev1764+9289a7c-cp34-cp34m-macosx_10_6_intel.whl.Toinstallthispackage,runacommandsuchasthefollowing:

$sudopipinstallwxPython_Phoenix-3.0.3.dev1764+9289a7c-cp34-cp34m-

macosx_10_6_intel.whl

TheoutputfromthiscommandshouldincludeSuccessfullyinstalledwxPython-Phoenix-3.0.3.dev1764+9289a7corasimilarline.

Now,wehaveallthenecessarydependenciesfordevelopingourprojectonMac.

Page 271: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Debiananditsderivatives,includingRaspbian,Ubuntu,andLinuxMintForPython2.7,therequiredlibrariesareinthestandardrepository.Toinstallthem,opentheterminalandrunthefollowingcommand:

$sudoapt-getinstallpython-opencvpython-scikits-learnpython-wxgtk2.8

Notethatthepython-opencvpackagedependsonthepython-numpypackage,sopython-numpywillalsobeinstalled.

Asanalternative,youcaninstallwxPythonPhoenixinsteadofthepython-wxgtk2.8package.TosetupwxPythonPhoenixanditsdependencies,trythefollowingcommands:

$sudoapt-getinstalllibwxbase3.0-devlibwxgtk3.0-devwx-common

libwebkit-devlibwxgtk-webview3.0-devwx3.0-exampleswx3.0-headerswx3.0-

i18nlibwxgtk-media3.0-dev

$pipinstall--upgrade--pre-fhttp://wxpython.org/Phoenix/snapshot-

builds/--trusted-hostwxpython.orgwxPython_Phoenix

However,ifindoubt,justusethepython-wxgtk2.8package.

Page 272: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Fedoraanditsderivatives,includingRHELandCentOSForPython2.7,therequiredlibrariesareinthestandardrepository.Toinstallthem,opentheterminalandrunthefollowingcommand:

$sudoyuminstallopencv-pythonpython-scikit-learnwxPython

Notethattheopencv-pythonpackagedependsonthenumpypackage,sonumpywillalsobeinstalled.

Page 273: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

OpenSUSEanditsderivativesAgain,forPython2.7,therequiredlibrariesareinthestandardrepository.Toinstallthem,opentheterminalandrunthefollowingcommand:

$sudoyuminstallpython-opencvpython-scikit-learnpython-wxWidgets

Notethatthepython-opencvpackagedependsonthepython-numpypackage,sopython-numpywillalsobeinstalled.

Page 274: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 275: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SupportingmultipleversionsofOpenCVOpenCV2.xandOpenCV3.xbothusethenamecv2fortheirtop-levelPythonmodule.However,insidethismodule,someclasses,functions,andconstantshavebeenrenamedinOpenCV3.x.Moreover,somefunctionalityisentirelynewinOpenCV3.x,butourprojectreliesonlyonfunctionalitythatispresentinbothmajorversions.

Tobridgeafewofthenamingdifferencesbetweenversions2.xand3.x,wecreateamodule,CVBackwardCompat.Itbeginsbyimportingcv2:

importcv2

OpenCV’sversionstringisstoredincv2.__version__.Forexample,itsvaluemaybe2.4.11or3.0.0.Wecanusethefollowinglineofcodetogetthemajorversionnumberasaninteger,suchas2or3:

CV_MAJOR_VERSION=int(cv2.__version__.split('.')[0])

ForOpenCV2.x(orearlier),wewillinjectnewnamesintotheimportedcv2modulesothatallthenecessaryOpenCV3.xnameswillbepresent.Specifically,weneedtocreatealiasesforseveralconstantsthathavenewnamesinOpenCV3.x,asseeninthefollowingcode:

ifCV_MAJOR_VERSION<3:

#CreatealiasestomakepartsoftheOpenCV2.xlibrary

#forward-compatible.

cv2.LINE_AA=cv2.CV_AA

cv2.CAP_PROP_FRAME_WIDTH=cv2.cv.CV_CAP_PROP_FRAME_WIDTH

cv2.CAP_PROP_FRAME_HEIGHT=cv2.cv.CV_CAP_PROP_FRAME_HEIGHT

cv2.FILLED=cv2.cv.CV_FILLED

ThisistheentireimplementationofCVBackwardCompat.OurothermoduleswillbeabletoimporttheCVBackwardCompatinstanceofcv2anduseanyofthealiasesthatwemayhaveinjected.Wewillseeanexampleinthenextmodule—ResizeUtils.

Page 276: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 277: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ConfiguringcamerasOpenCVprovidesaclass,calledcv2.VideoCapture,thatrepresentsastreamofimagesfromeitheravideofileoracamera.Thisclasshasmethodssuchasread(image)forexposingthestream’snextimageasaNumPyarray.Italsohastheget(propId)andset(propId,value)methodsforaccessingpropertiessuchasthewidthandheight(inpixels),colorformat,andframerate.Thevalidpropertiesandvaluesmaydependonthesystem’svideocodecsorcameradrivers.

Acrosscameras,thedefaultpropertyvaluesmaydifferdramatically.Forexample,onecameramightdefaulttoanimagesizeof640x480,whileanothermaydefaultto1920x1080.Forgreaterpredictability,weshouldtrytosetcrucialparametersratherthanrelyonthedefaults.Let’screateamodulecalledResizeUtilscontainingautilityfunctiontoconfiguretheimagesize.

TheResizeUtilsmodulebeginsbyimportingtheCVBackwardCompatinstanceofcv2,whichmaycontainaliases(dependingontheOpenCVversion).Hereistheimportstatement:

fromCVBackwardCompatimportcv2

ThepropertyIDsforwidthandheightarestoredinconstantscalledcv2.CAP_PROP_FRAME_WIDTHandcv2.CAP_PROP_FRAME_HEIGHT,respectively(referringtotheprevioussection,notethattheconstants’originalnamesinOpenCV2.xwerecv2.cv.CV_CAP_PROP_FRAME_WIDTHandcv2.cv.CV_CAP_PROP_FRAME_HEIGHT,butwecreatedaliasestomatchtheOpenCV3.xnames).Notetheuseoftheseconstantsinthefollowingimplementationoftheutilityfunction:

defcvResizeCapture(capture,preferredSize):

#Trytosettherequesteddimensions.

w,h=preferredSize

successW=capture.set(cv2.CAP_PROP_FRAME_WIDTH,w)

successH=capture.set(cv2.CAP_PROP_FRAME_HEIGHT,h)

ifsuccessWandsuccessH:

#Therequesteddimensionsweresuccessfullyset.

#Returntherequesteddimensions.

returnpreferredSize

#Therequesteddimensionsmightnothavebeenset.

#Returntheactualdimensions.

w=capture.get(cv2.CAP_PROP_FRAME_WIDTH)

h=capture.get(cv2.CAP_PROP_FRAME_HEIGHT)

return(w,h)

Asarguments,thefunctiontakesacv2.VideoCaptureobjectcalledcaptureandatwo-dimensionaltuplecalledpreferredSize.WetrytoconfigurecapturetousethepreferredwidthandheightinpreferredSize.Thepreferreddimensionsmayormaynotbesupported,soaswiththefeedback,wereturnatupleoftheactualwidthandheight.

TheResizeUtilsmodulewillbeusefulforourCheckersModelmodule,asCheckersModelisresponsibleforinstantiatingandreadingfromcv2.VideoCapture.

Page 278: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 279: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

WorkingwithcolorsNormally,whenOpenCVobtainsanimagefromafileorcamera,itputstheimageintheblue-green-red(BGR)colorformat.Morespecifically,theimageisa3DNumPyarrayinwhichimage[y][x][0]isapixel’sbluevalue(intherangeof0to255),image[y][x][1]isitsgreenvalue,andimage[y][x][2]isitsredvalue.Theyandxindicesstartfromthetop-leftcorneroftheimage.Ifweconvertanimageintograyscale,itbecomesa2DNumPyarray,inwhichimage[y][x]isapixel’sgrayscalevalue.

Let’swritesomeutilityfunctionsintheColorUtilsmoduletoworkwithcolordata.OurfunctionswillusePython’sstandardmathmoduleandNumPy,asseeninthefollowingimportstatements:

importmath

importnumpy

Let’swriteafunctionthatallowsustocopyasinglecolorchannelfromasourceimage(whichisinBGRformat)toadestinationimage(whichisingrayscaleformat).IfthespecifieddestinationimageisNoneoritsformatiswrong,ourfunctionwillcreateit.Tocopythechannel,wejusttakeaflatviewofthesourceimage,sliceitwithastrideof3,andassigntheresulttoafullsliceofthedestinationimage,asseeninthefollowingimplementation:

defextractChannel(src,channel,dst):

dstShape=src.shape[:2]

ifdstisNoneordst.shape!=dstShapeor\

dst.dtype!=numpy.uint8:

dst=numpy.empty(dstShape,numpy.uint8)

dst[:]=src.flatten()[channel::3].reshape(dstShape)

returndst

Fortypicalcheckerboardsundertypicallightingconditions,theredchannelshowsahighcontrastbetweendarkandlightsquares.Later,wewillusethisobservationtoouradvantage.

Ontheotherhand,partsofouranalysiswillrelyonfullcolorsinsteadofonlyonechannel.WewillneedtoquantifythecontrastordistancebetweentwoBGRcolorsinordertodecidewhetherasquarecontainsalightpiece,adarkpiece,ashadow,ornothing.AnaïveapproachistousetheEuclideandistance,whichwouldbeimplementedlikethis:

defcolorDist(color0,color1):

returnmath.sqrt(

(color0[0]-color1[0])**2+

(color0[1]-color1[1])**2+

(color0[2]-color1[2])**2)

However,thisapproachassumesthatallcolorchannelshavethesamescaleandthescaleislinear.Thisassumptiondefiescommonsense.Forexample,mostpeoplewouldagreethatthecoloramber(b=0,g=191,r=255)isnotashadeofred(b=0,g=0,r=255)butthatthecoloremerald(b=191,g=255,r=0)isashadeofgreen(b=0,g=255,r=0),eventhoughthesetwopairingsrepresentthesameEuclideandistance(191).Manyalternative

Page 280: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

formulationsassumethatthechannelsarestilllinearbuthavedifferentscales—typically,theyassumethatgreenhasthebiggestunit,followedbyred,andthenblue.Theseformulationsyieldhighcontrastbetweenyellowandblue(forexample,betweensunlightandshade),andyetyieldlowcontrastbetweenshadesofblue.Thisisstillratherunsatisfactorybecausemostpeoplearegoodatdistinguishingshadesofblue.ThiadmerRiemersmacomparesseveraldistanceformulationsathttp://www.compuphase.com/cmetric.htm,andproposesanalternativeinwhichthegreenscaleislinearwhiletheredandbluescalesarenonlinear,withreddifferenceshavingmoreweightincomparisonsofreddishcolorsandbluedifferenceshavingmoreweightincomparisonsofnon-reddishcolors.Let’sreplaceourpreviouscolorDistfunctionwiththefollowingimplementationofRiemersma’smethod:

defcolorDist(color0,color1):

#Calculateared-weightedcolordistance,asdescribed

#here:http://www.compuphase.com/cmetric.htm

rMean=int((color0[2]+color1[2])/2)

rDiff=int(color0[2]-color1[2])

gDiff=int(color0[1]-color1[1])

bDiff=int(color0[0]-color1[0])

returnmath.sqrt(

(((512+rMean)*rDiff*rDiff)>>8)+

4*gDiff*gDiff+

(((767-rMean)*bDiff*bDiff)>>8))

Basedontheprecedingformula,thedistancebetweenblackandwhiteisapproximately764.8.Thescaleofthisdistanceisnotintuitive.Wemightprefertoworkwithnormalizedvalues,wherebythenormalizeddistancebetweenblackandwhiteisdefinedas1.0.Thefollowingfunctionreturnsanormalizedcolordistance:

defnormColorDist(color0,color1):

#Normalizebasedonthedistancebetween(0,0,0)and

#(255,255,255).

returncolorDist(color0,color1)/764.8333151739665

NowwehavesufficientutilityfunctionstosupportourupcomingimplementationoftheCheckersModelmodule,whichwillcaptureandanalyzeimages.

Page 281: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 282: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

BuildingtheanalyzerTheCheckersModelmoduleistheeyesandthebrainofourproject.ItbringstogethereverythingexcepttheGUI.Specifically,itdependsonNumPy,OpenCV,scikit-learn,andourColorUtilsandResizeUtilsmodules,asreflectedinthefollowingimportstatements:

importnumpy

importsklearn.cluster

fromCVBackwardCompatimportcv2

importColorUtils

importResizeUtils

NoteAlthoughwearecombiningimagecapturingandanalysisintoonemodule,theyarearguablydistinctresponsibilities.Forthisproject,theyshareadependencyonOpenCV.However,inafutureproject,youmightcaptureimagesfromacamerathatrequiresanotherlibrary,orfromanentirelydifferenttypeofsource,suchasanetwork.Youmightevensupportawidevarietyofcapturingtechniquesinoneproject.Wheneveryoufeelthatimagecaptureisacomplexprobleminitsownright,considerdedicatingatleastoneseparatemoduletoit.

Tomakeourcodemorereadable,wewilldefineseveralconstantsinthismodule.Theseconstantsrepresentthepossiblestatesoftheboard’srotation,theshadows’direction,andeachsquare’sclassification.Herearetheirdefinitions:

ROTATION_0=0

ROTATION_CCW_90=1

ROTATION_180=2

ROTATION_CCW_270=3

DIRECTION_UP=0

DIRECTION_LEFT=1

DIRECTION_DOWN=2

DIRECTION_RIGHT=3

SQUARE_STATUS_UNKNOWN=-1

SQUARE_STATUS_EMPTY=0

SQUARE_STATUS_PAWN_PLAYER_1=1

SQUARE_STATUS_KING_PLAYER_1=11

SQUARE_STATUS_PAWN_PLAYER_2=2

SQUARE_STATUS_KING_PLAYER_2=22

ThismodulewillalsocontaintheCheckersModelclass,declaredlikethis:

classCheckersModel(object):

NotethatwehavemadeCheckersModelasubclassofobject.ThisisimportantforPython2.xcompatibilitybecausewearegoingtousepropertygetterandsettermethods,asdiscussedinthenextsubsection.UnlikePython3.x,Python2.xdoesnotsupport

Page 283: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

accessormethodsinallclasses,butratheronlyinsubclassesofobject.

Page 284: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ProvidingaccesstotheimagesandclassificationresultsThemembervariablesoftheCheckersModelclasswillincludecurrentimagesofthescene(thatis,everythingthatthewebcamcansee)inBGRandgrayscale,aswellasacurrentimageoftheboardinBGR.Unlikethescene,theboardwillbeabird’s-eyeviewandmaycontaintextinordertoshowtheclassificationresults.Wewillprovidepropertygetterssothatothermodulesmayreadtheimagesandtheirsizes,asseeninthefollowingcode:

@property

defsceneSize(self):

returnself._sceneSize

@property

defscene(self):

returnself._scene

@property

defsceneGray(self):

returnself._sceneGray

@property

defboardSize(self):

returnself._boardSize

@property

defboard(self):

returnself._board

NotePropertygettersandsetterssimplyprovideashorthandnotationsothatamethodlookslikeanon-callablevariable.Hereisanexamplethatdemonstrateshowtouseapropertygetter:

bm=BoardModel()

scene=bm.scene#Callsgetter,bm.scene()

Here,wehaveimplementedonlyagetterandnotasetterbecauseothermodulesshouldnotsettheimages.Thus,apieceofcodesuchasthefollowingwillproduceanerror:

bm.scene=None#Callssetter,bm.scene(None)

#Error!Thereisnosuchsetter.

Thenextsubsection,Providingaccesstoparametersfortheusertoconfigure,willgiveexamplesonhowtoimplementsettermethods.

Wealsoprovideagetterfortheclassificationresultsasa2DNumPyarray:

@property

defsquareStatuses(self):

returnself._squareStatuses

Forexample,ifPlayer1hasakinginthetop-leftcorneroftheboard,boardModel.squareStatuses[0,0]willbe11(thevalueoftheSQUARE_STATUS_KING_PLAYER_1constant,whichwedefinedearlier).

Page 285: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AlltheaforementionedpropertiesrepresenttheresultsoftheCheckersModelmodule’swork,soothermodulesshouldtreatthemasread-only(andthustheyhaveonlygetters,notsetters).Next,let’sconsiderotherpropertiesthatrepresenttheparametersoftheCheckersModelmodule’swork,andhavebothgettersandsetterssothatothermodulesmayreconfigurethem.

Page 286: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ProvidingaccesstoparametersfortheusertoconfigureTounderstandwhyCheckersModeloffersparametersthatarereconfigurableatruntime,let’slookatapreviewofthethingsthatwewillnotautomateinthisproject:

Thebird’s-eyeviewofthecheckerboardmayberotatedandflippedinawaydifferentfromwhattheuserexpects.ThisproblemisdiscussedintheCreatingandanalyzingthebird’s-eyeviewoftheboardsubsection.Weallowtheusertospecifyadifferentrotation(0,90,180,or270degrees)andflip(X,Y,neither,orboth).Theshadows’directionisnotdetectedautomatically.Bydefault,weassumethattheshadows’directionisup(negativey).Weallowtheusertospecifyadirection(up,right,down,orleft).Toclassifythecontentsofthesquares,wecomparethetwodominantcolorsinthesquare,andthiscomparisonreliesoncertainthresholdvalues.Forsomelightingconditionsandsomecolorsofcheckerssets,thedefaultthresholdsmightnotbeappropriate.Weallowtheusertospecifydifferentthresholds.TheprecisemeaningofeachthresholdisdiscussedintheAnalyzingthedominantcolorsinasquaresubsection.

Theboard’srotationisexpressedasaninteger,correspondingtooneoftheconstantsthatwedeclaredatthestartofthismodule.Thefollowingcodeimplementsthegettersandsettersfortherotation,andthesetterusesthemodulusoperatortoensurethattherotationswraparound:

@property

defboardRotation(self):

returnself._boardRotation

@boardRotation.setter

defboardRotation(self,value):

self._boardRotation=value%4

Thedirectionoftheshadowsisasimilarproperty:

@property

defshadowDirection(self):

returnself._shadowDirection

@shadowDirection.setter

defshadowDirection(self,value):

self._shadowDirection=value%4

Thethresholdsandflipdirectionsdonotrequireanyspeciallogicinagetterorsetter,sowewillsimplyimplementthemasplainoldmembervariables.Wecanseetheirdeclarationsinthefollowingcode,whichisthestartoftheclass’sinitializer:

def__init__(self,patternSize=(7,7),cameraDeviceID=0,

sceneSize=(800,600)):

self.emptyFreqThreshold=0.3

self.playerDistThreshold=0.4

self.shadowDistThreshold=0.45

Page 287: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self._boardRotation=ROTATION_0

self.flipBoardX=False

self.flipBoardY=False

self._shadowDirection=DIRECTION_UP

Let’sproceedtolookattherestofthemembervariablesandinitializationcode.

Page 288: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

InitializingtheentiremodelofthegameAswehavepreviouslydiscussed,theroleoftheCheckersModelclassistoproduceimagesofthesceneandboardandclassifythecontentsofeachsquare.Theremainderofthe__init__methoddeclaresvariablesthatpertaintoimagingandclassification.

TheimagesofthesceneandboardwillinitiallybeNone,asseenhere:

self._scene=None

self._sceneGray=None

self._board=None

Lookingbackattheprevioussubsection,notethatpatternSizeisoneoftheinitializer’sarguments.Thisvariablereferstothenumberofinternalcornersintheboard,suchas(7,7)inastandardAmericancheckerboardwitheightrowsandeightcolumns.Later,wewillseethatthenumberofinternalcornersisimportantforcertainOpenCVfunctions.Let’sputthecornerdimensionsandcountintothemembervariables,likethis:

self._patternSize=patternSize

self._numCorners=patternSize[0]*patternSize[1]

Later,asthestepstowardclassification,wewillmeasurethefrequencyanddistanceofthetwodominantcolorsineachsquare.ThistaskisdiscussedintheAnalyzingthedominantcolorsinasquaresubsection.Fornow,wewillonlycreateemptyNumPyarraysforthisdata:

self._squareFreqs=numpy.empty(

(patternSize[1]+1,patternSize[0]+1),

numpy.float32)

self._squareDists=numpy.empty(

(patternSize[1]+1,patternSize[0]+1),

numpy.float32)

Similarly,wewillcreateaNumPyarrayfortheresultsofclassification,andwewillfillinthisarraywiththeSQUARE_STATUS_UNKNOWNvalue(whichwepreviouslydefinedas-1):

self._squareStatuses=numpy.empty(

(patternSize[1]+1,patternSize[0]+1),

numpy.int8)

self._squareStatuses.fill(SQUARE_STATUS_UNKNOWN)

Tofindeachsquare’sdominantcolors,wewillrelyonaclassinscikit-learncalledsklearn.cluster.MiniBatchKMeans.Thisclassrepresentsastatisticalprocesscalledk-meansclustering,whichseparatesdataintoagivennumberofgroupsandfindsthecentroidofeachgroup.Fornow,weonlyneedtoconstructaninstanceoftheclasswithasingleargument,n_clusters,whichindicatesthenumberofgroupsthattheclustererwilldistinguish(2inthiscase,becausewewanttoknowthetwodominantcolors):

self._clusterer=sklearn.cluster.MiniBatchKMeans(2)

Tosupportwebcaminput,wewillcreateaVideoCaptureobjectandsetitscapturedimensionsusingourutilityfunctionfromtheResizeUtilsmodule:

Page 289: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

self._capture=cv2.VideoCapture(cameraDeviceID)

self._sceneSize=ResizeUtils.cvResizeCapture(

self._capture,sceneSize)

w,h=self._sceneSize

Later,aswecaptureimages,wewilltrytofindtheinternalcornersofthecheckerboard,andwewillstorethesuccessfullydetectedcornerssothatwedonothavetoperformthissearchfromscratchforeveryframe.Thefollowingtwomembervariableswillholdthepreviouslydetectedcorners(initiallyNone)andagrayscaleimageofthesceneatthetimeofthepreviousdetection(initiallyanemptyimage):

self._lastCorners=None

self._lastCornersSceneGray=numpy.empty(

(h,w),numpy.uint8)

Thescene’sdetectedcornercoordinateswillimplyahomography(amatrixthatdescribesadifferenceinperspective)whenwecomparethemwiththeknowncornercoordinatesinabird’s-eyeviewoftheboard.Likethecorners,thehomographydoesnotneedtoberecomputedforeveryframe,sowewillstoreitinthefollowingmembervariable(initiallyNone):

self._boardHomography=None

Wewillarbitrarilystipulatethatthebird’s-eyeviewoftheboardwillbenolargerthanthecapturedimageofthescene.Startingfromthisconstraint,wewillcalculatethesizeofasquareandtheboard:

self._squareWidth=min(w,h)//(max(patternSize)+1)

self._squareArea=self._squareWidth**2

self._boardSize=(

(patternSize[0]+1)*self._squareWidth,

(patternSize[1]+1)*self._squareWidth

)

Havingdeterminedthesizeofasquare,wewillcalculateasetofcornercoordinatesforthebird’s-eyeview.Weneedthecoordinatesofonlythoseinternalcornerswherefoursquaresmeet.Later,intheDetectingtheboard’scornersandtrackingtheirmotionsubsection,wewillcomparetheseidealcoordinatestotheinternalcornersofthecheckerboardthataredetectedinthesceneinordertofindthehomography.HereisourcodeforcreatingalistandthenaNumPyarrayofevenlyspacedcorners:

self._referenceCorners=[]

forxinrange(patternSize[0]):

foryinrange(patternSize[1]):

self._referenceCorners+=[[x,y]]

self._referenceCorners=numpy.array(

self._referenceCorners,numpy.float32)

self._referenceCorners*=self._squareWidth

self._referenceCorners+=self._squareWidth

ThisconcludestheinitializationoftheCheckersModelclass.Next,let’sconsiderhowtheimagesofthesceneandboardwillchange,alongwiththeresultsofclassification.

Page 290: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

UpdatingtheentiremodelofthegameOurCheckersModelclasswillprovidethefollowingupdatemethod,whichothermodulesmaycall:

defupdate(self,drawCorners=False,

drawSquareStatuses=True):

Specifically,theGUIapplication(intheCheckersmodule)willcallthisupdatemethod.Logically,theapplicationisresponsibleformanagingresourcesandensuringthateverythingremainsresponsive,soitisinabetterpositiontodecidewhenanupdateshouldoccur.Theupdatemethod’sdrawCornersargumentspecifieswhethertheresultsofcornerdetectionshouldbedisplayedinthescene.ThedrawSquareStatusesargumentspecifieswhethertheresultsofclassificationshouldbedisplayedinthebird’s-eyeviewoftheboard.

Theupdatemethodreliesonseveralhelpermethods.First,wewillcallahelperthattriestocaptureanewimageofthescene.Ifthisfails,wereturnFalsetoinformthecallerthatnoupdatehasoccurred:

ifnotself._updateScene():

returnFalse#Failure

Wewillproceedtosearchforthecheckerboardinthescene.Asuccessfulsearchwillproduceasetofcornercoordinatesfortheboard’ssquares.Moreover,thesecoordinateswillimplyahomography.Thefollowinglineofcodecallsahelpermethodthatisresponsibleforfindingthecornersandahomography:

self._updateBoardHomography()

Next,wewillcallahelpermethodthatisresponsibleforcreatingthebird’s-eyeviewoftheboardaswellasanalyzingandclassifyingeachsquare:

self._updateBoard()

Atthispoint,theboarddetectionandsquareclassificationarecomplete,butwemaystillneedtodisplayresultsdependingonthedrawCornersanddrawSquareStatusesarguments.Conveniently,OpenCVprovidesafunction,cv2.drawChessboardCorners(image,patternSize,corners,patternWasFound),todisplayasetofdetectedcornersinascenecontainingachessboardorcheckerboard.Hereisthewayweuseit:

ifdrawCornersandself._lastCornersisnotNone:

#Drawtheboard'sgrid.

cv2.drawChessboardCorners(

self._scene,self._patternSize,

self._lastCorners,True)

Wehaveanotherhelpermethodfordisplayingtheclassificationresultinagivensquare.Thefollowingcodeshowshowweiterateoversquaresandcallthehelper:

ifdrawSquareStatuses:

Page 291: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

foriinrange(self._patternSize[0]+1):

forjinrange(self._patternSize[1]+1):

self._drawSquareStatus(i,j)

Atthispoint,theupdatehassucceeded,andwereturnTruetoletthecallerknowthattherearenewresults:

returnTrue#Success

Let’sdelvedeeperintothehelpermethods’roles,startingwithimagecapture.

Page 292: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CapturingandconvertinganimageOpenCV’sVideoCaptureclasshasaread(image)method.Thismethodcapturesanimageandwritesittothegivendestinationarray(ifimageisNoneoritsformatiswrong,anewarrayiscreated).Themethodreturnsatuple,(retval,image),containingaboolean(Trueifthecapturehassucceeded)andthentheimage(eithertheoldarrayoranewlycreatedarray).Weusethismethodtotrytocaptureanewscenefromthecamera.Ifthissucceeds,weuseourextractChannelutilityfunctiontogettheredchannelasagrayscaleversionofthescene.Thefollowingmethodperformsthesestepsandreturnsabooleantoindicatesuccessorfailure:

def_updateScene(self):

success,self._scene=self._capture.read(self._scene)

ifnotsuccess:

returnFalse#Failure

#Usetheredchannelasgrayscale.

self._sceneGray=ColorUtils.extractChannel(

self._scene,2,self._sceneGray)

returnTrue#Success

Considerthefollowingstripofimages.TheleftmostimagerepresentstheoriginalsceneinBGRcolor(thoughitwillappearasgrayscaleinthisbook’sprintedition).Thisboard’scolorsareburntamberforthedarksquares,ochreforthelightsquares,andburntsiennafortheborder(thiscolorschemeisquitecommonincheckerboards).Thesecond,third,andfourthimages(fromlefttoright)representthescene’sblue,green,andredchannels,respectively:

Notethattheredchannelcapturesthecheckerboardbrightlyandwithgoodcontrastbetweenlighteranddarkersquares.Moreover,theboard’sborderappearsquitelight.Thisisgoodbecausewearesoongoingtouseadetectorthatisoptimizedforablack-and-whiteboardwithawhiteborder.

Page 293: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Detectingtheboard’scornersandtrackingtheirmotionWedonotwanttoupdatetheboard’scornersandhomographyineveryframe,butonlyinframeswheretheboardorcamerahasmoved.Thisrulereducesthecomputationalburdeninmostframesandallowsustokeepagooddetectionresultfromapreviousframesothatwedonotrequireanunobstructedviewoftheboardineveryframe.Particularly,wecanberelativelysureofgettinggooddetectionresultswhentheboardisempty,andwewouldwanttokeeptheseresultsforlaterframesinwhichtheboardisclutteredwithpiecesorwiththeplayers’movinghands.

Beforefindingnewcornersandthehomography,let’slookattrackingmotion.Supposewehavedetectedasetofcornersinapreviousframe.Ratherthansearchingtheentireimagefornewcorners,wecantrytofindthesamecornersatorneartheirpreviouslocations.Thisidea—mappingframe-to-framemotionofindividualpointsinanimage—iscalledopticalflow.OpenCVprovidesimplementationsofseveralopticalflowtechniques.WewilluseatechniquecalledpyramidalLukas-Kanade,whichisimplementedinthecv2.calcOpticalFlowPyrLK(prevImg,nextImg,prevPts)function.Thisfunctionreturnsatuple,(nextPts,status,error).Eachofthetuple’selementsisanarray.ThenextPtscontainsthepoints’estimatednewcoordinates.Thestatuscontainscodesforindicatingwhethereachestimateisvalid(1,meaningthepointwastracked)orinvalid(0,meaningitwaslost).Finally,errorcontainsameasurementofdissimilaritybetweenthepixelsintheoldandnewneighborhoodsforeachpoint.Ahighaverageerroracrossallpointsimpliesthateverythinglooksdifferentand,mostlikely,thattheboardorcamerahasmoved.Weapplythisreasoninginthefollowingcode,whichisthebeginningofthe_updateBoardHomographymethod:

def_updateBoardHomography(self):

ifself._lastCornersisnotNone:

corners,status,error=cv2.calcOpticalFlowPyrLK(

self._lastCornersSceneGray,

self._sceneGray,self._lastCorners,None)

#Statusis1iftracked,0ifnottracked.

numCornersTracked=sum(status)

#Ifnottracked,errorisinvalidsosetitto0.

error[:]=error*status

meanError=(sum(error)/numCornersTracked)[0]

ifmeanError<4.0:

#Theboard'soldcornersandhomographyare

#stillgoodenough.

return

NoteThethreshold,meanError<4.0,hasbeenchosenexperimentally.Ifyouseethattheestimateoftheboard’scornerschangesevenwhentheboardisstill,tryraisingthethreshold.Conversely,ifyoufindthattheestimateoftheboard’scornersremainsunchangedevenwhentheboardismoved,youmayneedtolowerthethreshold.

Page 294: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Ifwehavenotyetreturned,itmeansthateithertherearenopreviouslyfoundcorners,orthepreviouslyfoundcornersarenolongervalid(probablybecausetheboardorthecamerahasmoved).Eitherway,wemustsearchfornewcorners.OpenCVprovidesmanygeneral-purposefunctionsforfindinganykindofcorner,butconveniently,italsoprovidesaspecializedfunctionforfindingthecornersofsquaresinachessboardoracheckerboard.Thisfunction,cv2.findChessboardCorners(image,patternSize),canfindaboardofanyspecifieddimensions,evenanon-squareboard.ThepatternSizeargumentspecifiesthenumberofinternalcorners,suchas(7,7)forastandardAmericancheckerboardwitheightrowsandeightcolumns.Thisfunctionreturnsatuple,(retval,corners),whereretvalisaBoolean(whichisTrueifallthecornerswerefound)andcornersisalistofcornercoordinates.Itexpectsacheckerboardwithpureblackandpurewhitesquares,butothercolorschemesmayworkdependingonthestrengthoftheircontrastinthegivengrayscaleimage.Moreover,thefunctionexpectstheboardtohavealightborder,whichmakesiteasiertofindthecornersoftheoutermostdarksquares.ThefollowingcodeshowsourusageoffindChessboardCorners:

#Findthecornersintheboard'sgrid.

cornersWereFound,corners=cv2.findChessboardCorners(

self._sceneGray,self._patternSize,

flags=cv2.CALIB_CB_ADAPTIVE_THRESH)

NotethatfindChessboardCornershasanoptionalflagsargument.Weusedaflagcalledcv2.CALIB_CB_ADAPTIVE_THRESH,whichstandsforadaptivethresholding.Whenthisflagisset,thefunctionattemptstocompensatefortheoverallbrightnessoftheimagesothatitdoesnotnecessarilyrequirethesquarestolookreallyblackorreallywhite.

ThesmallcirclesandlinesshowninthefollowingimageareavisualizationofthecornersfoundusingfindChessboardCorners.TheyaredrawnusingthedrawChessboardCornersfunction,whichwecoveredintheprevioussubsection.

Page 295: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Ifthecornersarefound,wecanconverttheircoordinatesfromalisttoaNumPyarray.Then,wecancomparethefoundcoordinateswiththereferencecoordinatesforabird’s-eyeview(rememberthatwealreadyinitializedthereferencecoordinatesintheInitializingtheentiremodelofthegamesubsection).Tocomparethetwosetsofcoordinates,wewilluseanOpenCVfunctioncalledcv2.findHomography(srcPoints,dstPoints,method).Theoptionalmethodargumentrepresentsastrategytorejectoutliers(incongruouspointsthatshouldnotbecountedtowardtheresult).Thefunctionreturnsahomographymatrix,whichisatransformationthatmapssrcPointstodstPointswithminimalerror.ThefollowingcodedemonstratesouruseoffindHomography:

ifcornersWereFound:

#Findthehomography.

corners=numpy.array(corners,numpy.float32)

corners=corners.reshape(self._numCorners,2)

self._boardHomography,matches=cv2.findHomography(

corners,self._referenceCorners,cv2.RANSAC)

#Recordthecornersandtheirimage.

self._lastCorners=corners

self._lastCornersSceneGray[:]=self._sceneGray

Notethatwekeepacopyofthegrayscaleimageofthescene.Thenexttimethismethodiscalled,wewillcomparetheoldandnewgrayscalescenestotrackthecorners’motion.

Atthispoint,wemayhavefoundtheboard’shomography,butwehavenotyetcreatedanimagetoshowthebird’s-eyeview.Let’stacklethistasknext.

Page 296: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Creatingandanalyzingthebird’s-eyeviewoftheboardIftheboard’scornersandhomographyarefound,wecantransformthescene’sperspectivetoproduceabird’s-eyeviewoftheboard.TherelevantfunctioninOpenCViscv2.warpPerspective(src,M,dsize,dst),whereMisthehomographymatrixanddsizeisanarbitrarysizefortheoutputimage.Thisfunctionappliesthehomographymatrixtochangetheperspective,andthenitresizesandcropstheresult.Our_updateBoardmethodbeginswiththefollowingcode:

def_updateBoard(self):

ifself._boardHomographyisnotNone:

#Warptheboardtoobtainabird's-eyeview.

self._board=cv2.warpPerspective(

self._scene,self._boardHomography,

self._boardSize,self._board)

Thefollowingpairofimagesshowsthecamera’sviewofthescene(left),andtheresultingbird’s-eyeviewoftheboard(right)afterperspectivetransformation:

Atthisstage,thebird’s-eyeviewmightstillneedtoberotatedand/orflippedtomatchtheuser’ssubjectiveperceptionofdirections.Theuserisprobablysittingononesideoftheboardandperceivesthissideasthe“near”or“down”(positivey)side.Thecameramightbeonthesameside,anyotherside,oradiagonal!Moreover,dependingonitsdriversandconfiguration,thecameramayevencapturemirroredimages.ThefindChessboardCornersfunctiondoesnotlimititssearch.Asfarasitisconcerned,thescenecouldshowanupside-downandmirroredchessboard,andthissolutionisasgoodasanyothersolutionthatputsasetofcornersintherightplaces.Wecouldinspectandeditthevaluesinthe_boardHomographymatrixtoenforceourownassumptions,butinsteadofthis,wewilllettheuserinspecttheimageanddecideonanynecessarychanges.

Lookingcloselyattheprecedingpairofimages,notethattheboardhasadarkorblurryseamdownthemiddle(itcanfoldhere).Theseamisapproximatelyhorizontalinthecamera’sviewbutverticalinthebird’s-eyeview.Moreover,thetwoviewsdifferbyahorizontalflip.Ausermightfindthesedifferencesunintuitive.

Page 297: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

OpenCVprovidesafunctioncalledcv2.flip(src,flipCode,dst)toflipanimageinthemannerspecifiedbyflipCode(-1orlesstoflipbothxandycoordinates,0toflipycoordinates,and1orgreatertoflipxcoordinates).Anotherfunction,cv2.transpose(src,dst),servestoswapxandycoordinateswitheachother.Arotationof90or270degreescanbeimplementedasacombinationofatransposeandaflip,whilearotationof180degreescanbeimplementedasaflipinbothdimensions.Let’susetheseapproachesinthefollowingcodetoapplyaspecifiedflipandrotation:

#Rotateandfliptheboard.

flipX=self.flipBoardX

flipY=self.flipBoardY

ifself._boardRotation==ROTATION_CCW_90:

cv2.transpose(self._board,self._board)

flipX=notflipX

elifself._boardRotation==ROTATION_180:

flipX=notflipX

flipY=notflipY

elifself._boardRotation==ROTATION_CCW_270:

cv2.transpose(self._board,self._board)

flipY=notflipY

ifflipX:

ifflipY:

cv2.flip(self._board,-1,self._board)

else:

cv2.flip(self._board,1,self._board)

elifflipY:

cv2.flip(self._board,0,self._board)

Later,wewillensurethatusercansetboardRotation,flipBoardX,andflipBoardYviatheGUI.Forexample,theusercanseetheprecedingpairofimagesandthenmakeadjustmentstoproducethefollowingpairofimagesinstead:

Oncethetransformationsarecomplete,wewilliterateoverallthesquaresandcallahelpermethodtogeneratedataabouteachsquare’scolor:

foriinrange(self._patternSize[0]+1):

forjinrange(self._patternSize[1]+1):

self._updateSquareData(i,j)

Then,wewilliterateoverallthesquaresagainandcallahelpermethodtoupdatethe

Page 298: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

classificationofeachsquare:

foriinrange(self._patternSize[0]+1):

forjinrange(self._patternSize[1]+1):

self._updateSquareStatus(i,j)

Notethatasquare’sclassificationmayrelyondataaboutaneighboringsquare(sincewemaysearchforashadowtodistinguishakingfromapawn).Thisiswhyweanalyzethecolorsofallthesquaresinonestepandclassifythesquaresinanotherstep.Let’sconsidertheanalysisofcolorsnow.

Page 299: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

AnalyzingthedominantcolorsinasquareOur_updateSquareDatamethodtakesasquare’sindicesasarguments,anditbeginsbycalculatingthesquare’stop-leftandbottom-rightpixelcoordinatesinthe_boardimage,asseeninthefollowingcode:

def_updateSquareData(self,i,j):

x0=i*self._squareWidth

x1=x0+self._squareWidth

y0=j*self._squareWidth

y1=y0+self._squareWidth

Rememberthatwehaveamembervariablecalled_clusterer.Itisaninstanceofthesklearn.cluster.MiniBatchKMeansclass.Thisclasshasamethod,calledfit(X),thatclassifiesthedatainXandstorestheresultsinthemembervariablesofMiniBatchKMeans.Xmustbea2DNumPyarray,sowemustreshapethesquare’simagedata.Forexample,ifasquarehas75x75pixelswiththreecolorchannels,wewillpassaviewofthesquareasanarrayofshape(75*75,3),not(75,75,3).Thefollowingcodeshowshowwesliceandreshapethesquareandpassittothefitmethod:

#Findthetwodominantcolorsinthesquare.

self._clusterer.fit(

self._board[y0:y1,x0:x1].reshape(

self._squareArea,3))

Theresultsofthecolorclusteringarestoredin_clusterer.centersand_clusterer.labels,whichareNumPyarrays.Theshapeofcentersis(2,3),anditrepresentsthetwodominantBGRcolorsinthesquare.Meanwhile,labelsisaone-dimensionalarraywhoselengthisequaltothenumberofpixelsinthesquare.Eachvalueinlabelsis0ifthepixelisclusteredwiththefirstdominantcolor,or1ifthepixelisclusteredwiththeseconddominantcolor.Thus,themeanoflabelsrepresentsthesecondcolor’sfrequency(theproportionofpixelsthatareclusteredwiththiscolor).Thefollowingcodeshowshowwecanfindthefrequencyofthelessdominantcolor,aswellasthenormalizeddistancebetweenthetwodominantcolors,basedontheformulainourColorUtilsmodule:

#Findtheproportionofthesquare'sareathatis

#occupiedbythelessdominantcolor.

freq=numpy.mean(self._clusterer.labels_)

iffreq>0.5:

freq=1.0-freq

#Findthedistancebetweenthedominantcolors.

dist=ColorUtils.normColorDist(

self._clusterer.cluster_centers_[0],

self._clusterer.cluster_centers_[1])

self._squareFreqs[j,i]=freq

self._squareDists[j,i]=dist

Thefrequencyanddistanceenableustotalkaboutthesquare’scolorsatamuchhigher

Page 300: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

levelofabstractionthanrawchannelvalues.Forexample,wemayobserve,“Thissquarecontainsabigobjectthatcontrastsstronglywiththebackground,”(highfrequencyandhighdistance)withoutneedingtosearchforanyspecificsquarecolororplayingpiececolor.Next,wewillusesuchobservationstoclassifythecontentsofasquare.

Page 301: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ClassifyingthecontentsofasquareOur_updateSquareStatusesmethodtakesasquare’sindicesasarguments(again),anditbeginsbylookingupthesquare’sfrequencyanddistancedata,asseeninthiscode:

def_updateSquareStatus(self,i,j):

freq=self._squareFreqs[j,i]

dist=self._squareDists[j,i]

Wearealsointerestedinthefrequencyanddistancedataofaneighboringsquarethatmaypotentiallycontainashadow.Asdiscussedearlier,theusermayconfigureashadow’sdirection.Thefollowingcodeshowshowwecanselectaneighborbasedontheshadow’sdirection:

ifself._shadowDirection==DIRECTION_UP:

ifj>0:

neighborFreq=self._squareFreqs[j-1,i]

neighborDist=self._squareDists[j-1,i]

else:

neighborFreq=None

neighborDist=None

elifself._shadowDirection==DIRECTION_LEFT:

ifi>0:

neighborFreq=self._squareFreqs[j,i-1]

neighborDist=self._squareDists[j,i-1]

else:

neighborFreq=None

neighborDist=None

elifself._shadowDirection==DIRECTION_DOWN:

ifj<self._patternSize[1]:

neighborFreq=self._squareFreqs[j+1,i]

neighborDist=self._squareDists[j+1,i]

else:

neighborFreq=None

neighborDist=None

elifself._shadowDirection==DIRECTION_RIGHT:

ifi<self._patternSize[0]:

neighborFreq=self._squareFreqs[j,i+1]

neighborDist=self._squareDists[j,i+1]

else:

neighborFreq=None

neighborDist=None

else:

neighborFreq=None

neighborDist=None

Weexpectaking’sshadowtobeasmallregion(lowfrequency)thatcontrastsstrongly(highfrequency)withthebackgroundofalightsquare.Thus,wewilltestwhetherthefrequencyisbelowacertainthresholdandthatthedistanceisaboveanotherthreshold,asseeninthefollowingcode:

castsShadow=\

neighborFreqisnotNoneand\

Page 302: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

neighborFreq<self.emptyFreqThresholdand\

neighborDistisnotNoneand\

neighborDist>self.shadowDistThreshold

Rememberthattheusermayconfigurethethresholdsinordertomanuallyadaptourapproachtodifferentlightingconditionsandcolorschemes.

NoteNotethatthesquaresononeedgeoftheboardwillnothaveanyneighborsintheshadow’sdirection,sowejustassumethatthereisnoshadowthere.Wecouldimproveonourapproachbyanalyzingtheborderareasjustpasttheboard’sedge.

Atthispoint,wehaveanideaofwhethertheneighbormightbeashadowornot,butwestillneedtoconsiderthecurrentsquare.Weexpectaplayingpiecetobealargeobject(highfrequency),andintheabsenceofsuchanobject,thesquaremustbeempty.Thislogicisreflectedinthefollowingcode,whichreliesonafrequencythreshold:

iffreq<self.emptyFreqThreshold:

squareStatus=SQUARE_STATUS_EMPTY

else:

Aplayingpiecemaybeeitherdarkorlight,andeitherapawnoraking.Weexpectalightplayingpiecetocontraststrongly(highdistance)withthebackgroundofadarksquare,whileadarkplayingpiecewillhaveweakercontrast.Thus,anotherdistancethresholdistested.Moreover,weexpectakingtohavealongshadowthatextendsintoaneighboringsquare,whileapawnshouldhaveashortershadow.Thefollowingcodereflectsthesecriteria:

ifdist<self.playerDistThreshold:

ifcastsShadow:

squareStatus=SQUARE_STATUS_KING_PLAYER_1

else:

squareStatus=SQUARE_STATUS_PAWN_PLAYER_1

else:

ifcastsShadow:

squareStatus=SQUARE_STATUS_KING_PLAYER_2

else:

squareStatus=SQUARE_STATUS_PAWN_PLAYER_2

Atthispoint,wehaveaclassificationresult.Wewillstoreitsothatothermethods(andothermodules,viaapropertygetter)mayaccessit:

self._squareStatuses[j,i]=squareStatus

Next,wewillprovideaconvenientwaytovisualizetheclassificationsofallthesquares.

Page 303: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

DrawingtextAsthelaststepofupdatingtheimageoftheboard,wewilldrawtextatopeachnonemptysquaretoshowthenumericcodeoftheclassificationresult.OpenCVprovidesafunctioncalledcv2.putText(img,text,org,fontFace,fontScale,color,thickness,lineType)fordrawingtextatthepositionspecifiedbytheorg(origin)argument.Theoriginreferstothetop-leftcornerofthetext.However,wewantthetexttobecenteredinthesquare.Tofindthetext’soriginrelativetothesquare’scenter,weneedtoknowthesizeofthetextinpixels.Fortunately,OpenCVprovidesanotherfunction,cv2.getTextSize(text,fontFace,fontScale,thickness),forthispurpose.Thefollowingcodeusesthesetwofunctionstoplacethetextinthecenterofagivensquare:

def_drawSquareStatus(self,i,j):

x0=i*self._squareWidth

y0=j*self._squareWidth

squareStatus=self._squareStatuses[j,i]

ifsquareStatus>0:

text=str(squareStatus)

textSize,textBaseline=cv2.getTextSize(

text,cv2.FONT_HERSHEY_PLAIN,1.0,1)

xCenter=x0+self._squareWidth//2

yCenter=y0+self._squareWidth//2

textCenter=(xCenter-textSize[0]//2,

yCenter+textBaseline)

cv2.putText(self._board,text,textCenter,

cv2.FONT_HERSHEY_PLAIN,1.0,

(0,255,0),1,cv2.LINE_AA)

Notethatweusethecv2.FONT_HERSHEY_PLAINandcv2.LINE_AAconstantstoselecttheHersheyPlainfontandanti-aliasing.

ThiscompletesthefunctionalityoftheCheckersModelclass.Next,wewriteautilityfunctiontoconvertanOpenCVimageforusewithwxPython.Afterthis,wewillimplementtheGUIapplication.ItconfiguresaninstanceofCheckersModelanddisplaystheresultingimagesofthesceneandboard.

Page 304: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 305: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ConvertingOpenCVimagesforwxPythonAswehaveseenearlier,OpenCVtreatsimagesasNumPyarrays—typically,3DarraysinBGRformator2Darraysingrayscaleformat.Conversely,wxPythonhasitsownclassesforrepresentingimages,typicallyinRGBformat(thereverseofBGR).Theseclassesincludewx.Image(aneditableimage),wx.Bitmap(adisplayableimage),andwx.StaticBitmap(aGUIelementthatdisplaysaBitmap).

OurwxUtilsmodulewillprovideafunctionthatconvertsaNumPyarrayfromeitherBGRorgrayscaletoanRGBBitmap,readyfordisplayinawxPythonGUI.ThisfunctionalitydependsonOpenCVandwxPython,asreflectedinthefollowingimportstatements:

fromCVBackwardCompatimportcv2

importwx

Conveniently,wxPythonprovidesafactoryfunctioncalledwx.BitmapFromBuffer(width,height,dataBuffer),whichreturnsanewBitmap.ThisfunctioncanacceptaNumPyarrayinRGBformatasthedataBufferargument.However,abugcausesBitmapFromBuffertofailonthefirst-generationRaspberryPi,apopularsingle-boardcomputer(SBC).Asaworkaround,wecanuseapairoffunctions:wx.ImageFromBuffer(width,height,dataBuffer)andwx.BitmapFromImage(image).Thelatterislessefficient,soweshouldpreferablyuseitonlyonhardwarethatisaffectedbythebug.

Tocheckwhetherwearerunningonthefirst-generationPi,wecaninspectthenameofthesystem’sCPU,asseeninthefollowingcode:

#TrytodeterminewhetherweareonRaspberryPi.

IS_RASPBERRY_PI=False

try:

withopen('/proc/cpuinfo')asf:

forlineinf:

line=line.strip()

ifline.startswith('Hardware')and\

line.endswith('BCM2708'):

IS_RASPBERRY_PI=True

break

except:

pass

Next,let’slookatourconversionfunction’simplementationforthefirst-generationPi.First,wecheckthedimensionalityoftheNumPyarray,thenmakeaninformedguessaboutitsformat(BGRorgrayscale),andfinallyconvertittoanRGBarrayusinganOpenCVfunctioncalledcv2.cvtColor(src,code).Thecodeargumentspecifiesthesourceanddestinationformats,suchascv2.COLOR_BGR2RGB.Afterallofthis,weuseImageFromBufferandBitmapFromImagetoconverttheRGBarraytoanImageandtheImagetoaBitmap:

ifIS_RASPBERRY_PI:

Page 306: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

defwxBitmapFromCvImage(image):

iflen(image.shape)<3:

image=cv2.cvtColor(image,cv2.COLOR_GRAY2RGB)

else:

image=cv2.cvtColor(image,cv2.COLOR_BGR2RGB)

h,w=image.shape[:2]

wxImage=wx.ImageFromBuffer(w,h,image)

bitmap=wx.BitmapFromImage(wxImage)

returnbitmap

Theimplementationforotherkindsofhardwareissimilar,exceptthatweconverttheRGBarraydirectlytoaBitmapusingBitmapFromBuffer:

else:

defwxBitmapFromCvImage(image):

iflen(image.shape)<3:

image=cv2.cvtColor(image,cv2.COLOR_GRAY2RGB)

else:

image=cv2.cvtColor(image,cv2.COLOR_BGR2RGB)

h,w=image.shape[:2]

#ThefollowingconversionfailsonRaspberryPi.

bitmap=wx.BitmapFromBuffer(w,h,image)

returnbitmap

WewillusethisfunctioninourGUIapplication,comingupnext.

Page 307: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 308: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

BuildingtheGUIapplicationTheCheckersmodulewillcontainallofthecoderequiredfortheGUIapplication.ThismoduledependsonwxPythonaswellasPython’sstandardthreadingmoduletoallowustoputalloftheintensivecomputervisionworkontoabackgroundthread.Moreover,wewillrelyonourCheckersModelmoduleforthecapturingandanalysisofimages,andourWxUtilsmoduleforitsimageconversionutilityfunction.Herearetherelevantimportstatements:

importthreading

importwx

importCheckersModel

importWxUtils

Ourapplicationclass,Checkers,isasubclassofwx.Frame,whichrepresentsanormalwindow(notadialog).WeinitializeitwithaninstanceofCheckersModel,andawindowtitle(Checkersbydefault).Herearethedeclarationsoftheclassandthe__init__method:

classCheckers(wx.Frame):

def__init__(self,checkersModel,title='Checkers'):

WewillalsostoreCheckersModelinamembervariable,likethis:

self._checkersModel=checkersModel

Theimplementationoftheinitializercontinuesinthefollowingsubsections.WewillseehowtheapplicationlaysouttheGUI,handlesevents,andinteractswithCheckersModel.

Page 309: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CreatingawindowandbindingeventsWewillcreateawindowbyinitializingthewx.Framesuperclass.Ratherthanusethedefaultwindowstyle,wewillspecifyacustomstylethatdoesnotallowthewindowtoberesized.Wewillalsospecifythewindow’stitleandagraybackgroundcolorinthiscode:

style=wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.CAPTION|\

wx.SYSTEM_MENU|wx.CLIP_CHILDREN

wx.Frame.__init__(self,None,title=title,style=style)

self.SetBackgroundColour(wx.Colour(232,232,232))

MostwxPythonclassesinheritaBind(event,handler)methodfromahigh-levelclasscalledEvtHandler.Themethodregistersagivencallbackfunction(handler)foragiventypeofGUIevent(event).Whentheobjectreceivesaneventofthegiventype,thecallbackisinvoked.Forexample,let’saddthefollowinglineofcodetoensurethatagivenmethodiscalledwhenthewindowisclosed:

self.Bind(wx.EVT_CLOSE,self._onCloseWindow)

TheprecedingcallbackisimportantbecauseourCheckersclassneedstodosomecustomcleanupasitcloses.

Wecanalsogiveeventbindingsanidentifierandconnectthesebindingstokeyboardshortcutsviathewx.AcceleratorTableclass.Forexample,let’saddthiscodetobindacallbacktotheEsckey:

quitCommandID=wx.NewId()

self.Bind(wx.EVT_MENU,self._onQuitCommand,

id=quitCommandID)

acceleratorTable=wx.AcceleratorTable([

(wx.ACCEL_NORMAL,wx.WXK_ESCAPE,

quitCommandID)

])

self.SetAcceleratorTable(acceleratorTable)

Later,wewillimplementthecallbacksuchthattheEsckeymakesthewindowclose.

NowthatwehaveawindowandabasicgraspofwxPythoneventbinding,let’sstartputtingGUIelementsinthere!

Page 310: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CreatingandlayingoutimagesintheGUIThewx.StaticBitmapclassisaGUIelementthatdisplaysawx.Bitmap.Usingthefollowingcode,let’screateapairofStaticBitmapforourimagesofthesceneandtheboard:

self._sceneStaticBitmap=wx.StaticBitmap(self)

self._boardStaticBitmap=wx.StaticBitmap(self)

Later,wewillimplementahelpermethodtoshowtheCheckersModel'slatestimages.WewillalsocallthishelpernowtoinitializetheBitmapsofStaticBitmap:

self._showImages()

TodefinethelayoutsoftheStaticBitmapsandotherwxPythonwidgets,wemustaddthemtoakindofcollectioncalledwx.Sizer.Ithasseveraldirectandindirectsubclasses,suchaswx.BoxSizer(asimplehorizontalorverticallayout)andwx.GridSizer.Forthisproject,wewillusewx.BoxSizeronly.ThefollowingcodeputsourtwoStaticBitmapsinahorizontallayout,withthesceneimagefirst(leftmost)andtheboardimagesecond:

videosSizer=wx.BoxSizer(wx.HORIZONTAL)

videosSizer.Add(self._sceneStaticBitmap)

videosSizer.Add(self._boardStaticBitmap)

TherestoftheGUIwillconsistofbuttons,aradiobox,andsliders,allforthepurposeofenablingtheusertoconfiguretheCheckersModel.

Page 311: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

CreatingandlayingoutcontrolsBydesign,severalpropertiesofourCheckersModelclassmustbeconfiguredinteractively.Apersonmustadjusttheseparameterswhileviewingtheresultsofboarddetectionandsquareclassification.Adjustmentsshouldbenecessaryonlyaftertheboardisinitiallydetectedandafteranymajorchangesinthelighting.

Rememberthattheboarddetectordoesnotlimititssearchtoanyrangeofrotations,anditmaychoosearotationinanyquadrant.Theusermaywishtochangetherotationtomatchhisorhersubjectiveideaofwhichwaytheboardisfacing.Usingthefollowingcode,wewillcreateabuttonlabeledRotateboardCCW,andwewillbindittoacallbackthatwillberesponsibleforadjustingtherotationbyincrementsof90degreescounterclockwise:

rotateBoardButton=wx.Button(

self,label='RotateboardCCW')

rotateBoardButton.Bind(

wx.EVT_BUTTON,

self._onRotateBoardClicked)

Alsorememberthatthedetectormayarbitrarilyfliptheboard,sinceitmakesnoassumptionaboutwhetherornotthecameraismirrored.Let’screateandbindtheFlipboardXandFlipboardYbuttonstogivetheuseradditionalcontrolovertheperspective:

flipBoardXButton=wx.Button(

self,label='FlipboardX')

flipBoardXButton.Bind(

wx.EVT_BUTTON,

self._onFlipBoardXClicked)

flipBoardYButton=wx.Button(

self,label='FlipboardY')

flipBoardYButton.Bind(

wx.EVT_BUTTON,

self._onFlipBoardYClicked)

Asthesquareclassifierreliesontheshadows’directiontodeterminewherethetallkingpieceslie,weenabletheusertospecifytheshadows’directionusingaradiobox(asetofradiobuttons).TheboxislabeledShadowdirectionandtheoptionsareup,left,down,andright,asspecifiedinthefollowingcode:

shadowDirectionRadioBox=wx.RadioBox(

self,label='Shadowdirection',

choices=['up','left','down','right'])

shadowDirectionRadioBox.Bind(

wx.EVT_RADIOBOX,

self._onShadowDirectionSelected)

Notethattheradiobuttonsintheboxarecollectivelyboundtoonecallback,whichisinvokedwheneveranybuttonispressed.

Lastamongthecontrols,wewillprovidesliderstoconfiguretheclassifier’sthresholdvalues.Thesedefineitsexpectationsaboutthecolorcontrastindifferentkindsofsquares.

Page 312: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Thecodeshownherecallsahelpermethodtomakeandbindthreesliders,labeledEmptythreshold,Playerthreshold,andShadowthreshold:

emptyFreqThresholdSlider=self._createLabeledSlider(

'Emptythreshold',

self._checkersModel.emptyFreqThreshold*100,

self._onEmptyFreqThresholdSelected)

playerDistThresholdSlider=self._createLabeledSlider(

'Playerthreshold',

self._checkersModel.playerDistThreshold*100,

self._onPlayerDistThresholdSelected)

shadowDistThresholdSlider=self._createLabeledSlider(

'Shadowthreshold',

self._checkersModel.shadowDistThreshold*100,

self._onShadowDistThresholdSelected)

NoteElsewhereintheCheckersclass,weimplementthefollowingmethodtohelpcreatesliders:

def_createLabeledSlider(self,label,initialValue,callback):

slider=wx.Slider(self,size=(180,20))

slider.SetValue(initialValue)

slider.Bind(wx.EVT_SLIDER,callback)

staticText=wx.StaticText(self,label=label)

sizer=wx.BoxSizer(wx.VERTICAL)

sizer.Add(slider,0,wx.ALIGN_CENTER_HORIZONTAL)

sizer.Add(staticText,0,wx.ALIGN_CENTER_HORIZONTAL)

returnsizer

Allourcontrolswillsharecertainlayoutproperties.Theywillbecenteredverticallyintheirsizerandwillhave8pixelsofpaddingontheirright-handside(toseparateeachcontrolfromitsneighbor).Let’sdeclarethesepropertiesonce,likethis:

controlsStyle=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT

controlsBorder=8

Usingtheseproperties,let’saddallthecontrolstoahorizontalsizer,asseeninthefollowingcode:

controlsSizer=wx.BoxSizer(wx.HORIZONTAL)

controlsSizer.Add(rotateBoardButton,0,

controlsStyle,controlsBorder)

controlsSizer.Add(flipBoardXButton,0,

controlsStyle,controlsBorder)

controlsSizer.Add(flipBoardYButton,0,

controlsStyle,controlsBorder)

controlsSizer.Add(shadowDirectionRadioBox,0,

controlsStyle,controlsBorder)

controlsSizer.Add(emptyFreqThresholdSlider,0,

controlsStyle,controlsBorder)

controlsSizer.Add(playerDistThresholdSlider,0,

controlsStyle,controlsBorder)

controlsSizer.Add(shadowDistThresholdSlider,0,

controlsStyle,controlsBorder)

Page 313: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Nowwehaveallourcontrolsinarow!

Page 314: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

NestinglayoutsandsettingtherootlayoutSizerscanbenested.Let’screateaverticalsizerandaddourtwoprevioussizerstoitsothatourimageswillappearfirst(topmost)andourcontrolssecond:

rootSizer=wx.BoxSizer(wx.VERTICAL)

rootSizer.Add(videosSizer)

rootSizer.Add(controlsSizer,0,wx.EXPAND|wx.ALL,

border=controlsBorder)

Thewindowshouldadoptasizerastherootofthelayoutandshouldresizeitselftofitthislayout.Thenextlineofcodedoesthis:

self.SetSizerAndFit(rootSizer)

Now,wehaveaGUIwithalayoutandsomeeventbindings,buthowdowestartrunningupdatestothecheckersanalyzer?

Page 315: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

StartingabackgroundthreadTheCheckersModelclassencapsulatesalloftheheavyprocessinginthisproject,particularlythecaptureandanalysisofimages.Ifweranitsupdate()methodonthemainthread(whichwxPythonusesforGUIevents),theGUIwouldbecomeunresponsive,becauseupdate()wouldhogtheprocessorallthetime.Thus,wemustcreateabackgroundthreadthatupdate()maysafelymonopolize.ThefollowingcodeusesPython’sstandardthreading.Threadclasstorunagivenfunctiononanewthread:

self._captureThread=threading.Thread(

target=self._runCaptureLoop)

self._running=True

self._captureThread.start()

Notethatweinitializedamembervariable,_running,beforewestartedthethread.Later,intheimplementationofthethread’sfunction,wewillusethe_runningvariabletocontroltheterminationofaloop.

Alittlelater,wewillseetheimplementationofthe_runCaptureLoop()method,whichwehaveassignedtorunonthebackgroundthread.First,let’slookattheimplementationsofthecallbackmethodsthatwehaveboundtovariousGUIevents.

Page 316: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ClosingawindowandstoppingabackgroundthreadWhenthewindowcloses,weneedtoensurethatthebackgroundthreadterminatesnormally,meaningthatthethread’sfunctionmustreturn.Thethreading.Threadclasshasamethodcalledjoin()thatblocksthecaller’sthreaduntilthecalleethreadreturns.Thus,itallowsustowaitforanotherthread’scompletion.Aswewillseelater,ourbackgroundthreadcontinuesuntil_runningisFalse,sowemustsetthisbeforecallingjoin().Finally,wemustcleanupthewx.FrameobjectbycallingitsDestroy()method.Hereisthecodefortherelevantcallback:

def_onCloseWindow(self,event):

self._running=False

self._captureThread.join()

self.Destroy()

NoteAlleventcallbacksinwxPythonrequireaneventargument.Dependingonthetypeofevent,theeventargumentmayhavepropertiesthatgivedetailsabouttheuser’sinputorothercircumstances.

Thewindowwillclose(andtheprecedingcallbackwillbecalled)whentheuserclicksonthestandardclosebutton(X),orwhenwecallthewx.FrameobjectsClose()method.RememberthatwewantthewindowtoclosewhentheuserpressestheEsckey.Thus,theEsckeyismappedtothefollowingcallback:

def_onQuitCommand(self,event):

self.Close()

Therestofourcallbacksrelatetocontrolsthatrepresenttheanalyzer’sconfiguration.

Page 317: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ConfiguringtheanalyzerbasedonuserinputRememberthattheCheckersModelclasshasaboardRotationproperty.Thisisanintegerintherangeof0to3,representingcounterclockwiserotationin90-degreeincrements.Theproperty’ssetterusesamodulusoperatortoensurethatavalueof4circlesbackto0,andsoon.Thus,whentheuserclicksontheGUI’sRotateboardCCWbutton,wecansimplyincrementboardRotation,likethis:

def_onRotateBoardClicked(self,event):

self._checkersModel.boardRotation+=1

Similarly,whentheuserclicksontheFlipboardXorFlipboardYbutton,wecannegatethevalueoftheflipBoardXorflipBoardYproperty(rememberthatthesepropertiesarebooleans).Herearetherelevantcallbacks:

def_onFlipBoardXClicked(self,event):

self._checkersModel.flipBoardX=\

notself._checkersModel.flipBoardX

def_onFlipBoardYClicked(self,event):

self._checkersModel.flipBoardY=\

notself._checkersModel.flipBoardY

LiketheboardRotationproperty,theshadowDirectionpropertyisanintegerthatrepresentsacounterclockwiserotationin90-degreeincrements.TheoptionsintheShadowdirectioncontrolboxarearrangedinthesameorder(up,right,down,andleft).Conveniently,wxPythongivestheselectedoption’sindextothecallbackinevent.Selection.WecanassignthisindextotheshadowDirectionproperty,asseeninthefollowingcode:

def_onShadowDirectionSelected(self,event):

self._checkersModel.shadowDirection=event.Selection

Thethresholdproperties(emptyFreqThreshold,playerDistThreshold,andshadowDistThreshold)arefloating-pointnumbersintherangeof0.0to1.0.Bydefault,wxPython’sslidersinterprettheuser’sinputasanintegerintherangeof0to100.Thus,whentheusermovesaslider,wechangethecorrespondingthresholdtoone-hundredthoftheslider’svalue,asseeninthesecallbacks:

def_onEmptyFreqThresholdSelected(self,event):

self._checkersModel.emptyFreqThreshold=\

event.Selection*0.01

def_onPlayerDistThresholdSelected(self,event):

self._checkersModel.playerDistThreshold=\

event.Selection*0.01

def_onShadowDistThresholdSelected(self,event):

self._checkersModel.shadowDistThreshold=\

event.Selection*0.01

Astheuserchangestheproperties,theeffectontheclassificationshouldbevisibleinrealtime,orwithonlyamomentarylag.Let’sconsiderhowtoupdateanddisplaytheresults.

Page 318: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

UpdatingandshowingimagesOnthebackgroundthread,theapplicationwillcontinuallyaskitsCheckersModelinstancetoupdatetheimagesandtheanalysis.Wheneveranupdatesucceeds,theapplicationmustalteritsGUItoshowthenewimages.ThisGUIeventmustbeprocessedonthemain(GUI)thread;otherwise,theapplicationwillcrash,becausetwothreadscannotaccesstheGUIobjectsatonce.Conveniently,wxPythonprovidesafunctioncalledwx.CallAfter(callableObj)tocallatargetfunction(orsomeothercallableobject)onthemainthread.Thefollowingcodeimplementsourbackgroundthread’sloop,whichterminateswhen_runningisFalse:

def_runCaptureLoop(self):

whileself._running:

ifself._checkersModel.update():

wx.CallAfter(self._showImages)

Onthemainthread,wegettheCheckersModelimagesoftheunprocessedsceneandtheprocessedboard,convertthemtowxPython’sBitmapformatusingourutilityfunction,anddisplaythemintheapplication’sStaticBitmap.Thesetwomethodscarryoutthiswork:

def_showImages(self):

self._showImage(

self._checkersModel.scene,self._sceneStaticBitmap,

self._checkersModel.sceneSize)

self._showImage(

self._checkersModel.board,self._boardStaticBitmap,

self._checkersModel.boardSize)

def_showImage(self,image,staticBitmap,size):

ifimageisNone:

#Provideablackbitmap.

bitmap=wx.EmptyBitmap(size[0],size[1])

else:

#Converttheimagetobitmapformat.

bitmap=WxUtils.wxBitmapFromCvImage(image)

#Showthebitmap.

staticBitmap.SetBitmap(bitmap)

ThisistheendoftheCheckersclass,butwestillneedonemorefunctionintheCheckersmodule.

Page 319: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

RunningtheapplicationLet’swriteamain()functiontolaunchaninstanceoftheCheckersapplicationclass.Thisfunctionmustalsocreatetheapplication’sinstanceofCheckersModel.Wewillallowtheusertospecifythecameraindexasacommand-lineargument.Forexample,iftheuserrunsthefollowingcommand,CheckersModelwilluseacameraindexof1:

$pythonCheckers.py1

Hereisthemain()function’simplementation:

defmain():

importsys

iflen(sys.argv)<2:

cameraDeviceID=0

else:

cameraDeviceID=int(sys.argv[1])

checkersModel=CheckersModel.CheckersModel(

cameraDeviceID=cameraDeviceID)

app=wx.App()

checkers=Checkers(checkersModel)

checkers.Show()

app.MainLoop()

if__name__=='__main__':

main()

That’sallforthecode!Let’sgrabawebcamandacheckersset!

Page 320: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 321: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Troubleshootingtheprojectinreal-worldconditionsAlthoughthisprojectshouldworkwithmanycheckerssets,cameraperspectives,andlightingsetups,itstillrequirestheusertosetthestagecarefully,inaccordancewiththefollowingguidelines:

1. Ensurethatthewebcamandtheboardarestationary.Thewebcamshouldbeonatripodorsomeothermount.Theboardshouldalsobesecuredinplace,oritshouldbesufficientlyheavysothatitdoesnotmovewhentheplayerstouchit.

2. Leavetheboardemptyuntiltheapplicationdetectsitanddisplaysthebird’s-eyeview.Ifthereareplayingpiecesontheboard,theywillprobablyinterferewiththeinitialdetection.

3. Forbestresults,useablack-and-whitecheckerboard.Othercolorschemes(suchasdarkwoodandlightwood)maywork.

4. Forbestresults,useaboardwithalightborderaroundtheplayingarea.5. Also,forbestresults,useaboardwithamattefinishornofinish.Reflectionsona

glossyboardwillprobablyinterferewiththeanalysisofthedominantcolorsinsomesquares.

6. Puttheplayingpiecesonthedarksquares.7. Useplayingpiecesthatarenotofthesamecolorasthedarksquares.Forexample,on

ablackandwhiteboard,useredandwhite(orredandgray)pieces.8. Useplayingpiecesthataresufficientlytall.Aking(apairofstackedpieces)must

castashadowonanadjacentsquare.9. Ensurethatthescenehasbrightlightcomingpredominantlyfromonedirection,such

asdaylightfromawindow.Alsoensurethattheboardisalignedsuchthatthelightisapproximatelyparalleltothexorygridlines.Thus,akings’shadowwillfallonanadjacentlightsquare.

Experimentwithvariousreal-worldconditionsandvarioussettingsintheapplicationtoseewhatworksandwhatfails.Everycomputervisionsystemhaslimits.Likeaperson,itdoesnotseeeverythingclearly,anditdoesnotunderstandallthatitsees.Aspartofourtestingprocess,weshouldalwaysstrivetofindthesystem’slimits.Thiswilldriveustoadoptorinventmorerobusttechniquesforourfuturework.

Page 322: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 323: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

FurtherreadingonOpenCVOurcheckersapplicationgivesaglimpseoftheworldofcomputervision.Yet,thereismuchmoretosee!ForPythonprogrammers,thefollowingbooksfromPacktPublishingrevealabroadrangeofOpenCV’sfunctionality,withimpressiveapplications:

LearningOpenCV3ComputerVisionwithPython(October2015),byJoeMinichinoandJosephHowse:ThisisagrandtourofOpenCV’sPythoninterfaceandtheunderlyingtheoriesincomputervision,machinelearning,andartificialintelligence.Withagentlelearningcurve,itissuitableforeitherbeginnersorthosewhowanttoroundouttheirknowledgeofthelibraryanditsuses.RaspberryPiComputerVisionProgramming(May2015),byAshwinPajankar:Thisbeginner-friendlybookemphasizestechniquesthatarepracticalforlow-cost,low-poweredplatformssuchastheRaspberryPisingle-boardcomputers.OpenCVforSecretAgents(January,2015),byJosephHowse:Thisisacollectionofcreative,adventurousprojectsforintermediatetoadvanceddeveloperswhomaybenewtocomputervision.Ifyouwanttoperformabiometricrecognitionofacatorseepeople’sheartbeatsthroughamotion-amplifyingwebcam,thenthisisthe(only)bookforyou!

Besidestheseoptions,PacktPublishingoffersmorethanadozenotherbooksonOpenCVandcomputervisionforC++,Java,Python,iOS,andAndroiddevelopers.Wehopetomeetyouagaininanexplorationofthisexcitingandburgeoningtopic!

Page 324: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.
Page 325: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

SummaryWhileanalyzingagameofcheckers,weencounteredseveralfundamentalaspectsofcomputervisioninthefollowingtasks:

CapturingimagesfromacameraPerformingcomputationsoncolorandgrayscaledataDetectingandrecognizingasetoffeatures(thecornersoftheboard’ssquares)TrackingmovementsofthefeaturesSimulatingadifferentperspective(abird’s-eyeviewoftheboard)Classifyingtheregionsoftheimage(thecontentsoftheboard’ssquares)

WealsosetupandusedOpenCVandotherlibraries.Havingbuiltacompleteapplicationusingtheselibraries,youareinabetterpositiontounderstandthepotentialofcomputervisionandplanfurtherstudiesandprojectsinthisfield.

ThischapteralsoconcludesourjourneytogetherinPythonGameProgrammingbyExample.Webuiltmodernversionsofclassicvideogamesaswellasananalyzerforaclassicboardgame.Alongtheway,yougainedpracticalexperienceinmanyofthemajorgameengines,graphicsAPIs,GUIframeworks,andscientificlibrariesforPython.Theseskillsaretransferabletootherlanguagesandlibraries,andwillserveasyourfoundationagainandagainasyoubuildyourowngamesandotherpiecesofvisualsoftware!

Gocreatesomemoregreatprojects,sharethefun,andstayintouchwithus(AlejandroRodasdePazat<[email protected]>andJosephHowseathttp://nummist.com/)!

Page 326: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

IndexA

AARectShapeclass/ProcessingcollisionsActor/SpaceInvadersdesignadd(obj)method/Processingcollisionsadd_ballmethod/AddingtheBreakoutitemsadd_brickmethod/AddingtheBreakoutitemsanalyzer

building/Buildingtheanalyzeraccess,providingtoimages/Providingaccesstotheimagesandclassificationresultsaccess,providingtoclassificationresults/Providingaccesstotheimagesandclassificationresultsaccess,providingtoparametersforuserconfiguration/Providingaccesstoparametersfortheusertoconfigureentiregamemodel,initializing/Initializingtheentiremodelofthegameentiregamemodel,updating/Updatingtheentiremodelofthegameimage,capturing/Capturingandconvertinganimageimage,converting/Capturingandconvertinganimageboard’scorners,detecting/Detectingtheboard’scornersandtrackingtheirmotionboard’smotion,tracking/Detectingtheboard’scornersandtrackingtheirmotionbird’s-eyeview,creating/Creatingandanalyzingthebird’s-eyeviewoftheboarddominantcolors,analyzinginsquare/Analyzingthedominantcolorsinasquarecontentsofasquare,classifying/Classifyingthecontentsofasquaretext,drawing/Drawingtext

ApplePython/MacArbiter/Addingphysicsarrivalbehavior/Arrival

Page 327: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Bball

movement/Movementandcollisionscollisions/Movementandcollisions

Ballclass/TheBallclassbasicgameobjects

adding,togamelayer/ThegamelayerBreakoutgame

overview/AnoverviewofBreakoutplaying/PlayingBreakout

Breakoutitemsadding/AddingtheBreakoutitemsgameobjectinstantiation/AddingtheBreakoutitemskeyinputbinding/AddingtheBreakoutitems

Brickclass/TheBrickclass

Page 328: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Ccameras

configuring/ConfiguringcamerasCanvaswidget

about/DivingintotheCanvaswidgetcanvaswidget

canvas.coords()method/DivingintotheCanvaswidgetcanvas.move()method/DivingintotheCanvaswidgetcanvas.delete()method/DivingintotheCanvaswidgetcanvas.winfo_width()method/DivingintotheCanvaswidgetcanvas.itemconfig()method/DivingintotheCanvaswidgetcanvas.bind()method/DivingintotheCanvaswidgetcanvas.unbind()method/DivingintotheCanvaswidgetcanvas.create_text()method/DivingintotheCanvaswidgetcanvas.find_withtag()method/DivingintotheCanvaswidgetcanvas.find_overlapping()method/DivingintotheCanvaswidgetreferences,URL/DivingintotheCanvaswidget

Checkersapplicationplanning/PlanningtheCheckersapplication

check_collisionsmethod/StartingthegameChimpunk2D

URL/IntroducingPymunkCircleShapeclass/Processingcollisionsclear()method/ProcessingcollisionsCocos2d

actions/Cocos2dactionscocos2d

installing/Installingcocos2dabout/Gettingstartedwithcocos2dscene/Gettingstartedwithcocos2dlayer/Gettingstartedwithcocos2dsprite/Gettingstartedwithcocos2ddirector/Gettingstartedwithcocos2dclassmembers/Gettingstartedwithcocos2dfullscreenparameter/Gettingstartedwithcocos2dresizableparameter/Gettingstartedwithcocos2dvsyncparameter/Gettingstartedwithcocos2dwidthparameter/Gettingstartedwithcocos2dheightparameter/Gettingstartedwithcocos2dcaptionparameter/Gettingstartedwithcocos2dvisibleparameter/Gettingstartedwithcocos2duserinput,handling/Handlinguserinputscene,updating/Updatingthescene

Page 329: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

collisions,processing/ProcessingcollisionsShootactor/Shoot’emup!HUD,adding/AddinganHUDmysteryship/Extrafeature–themysteryship

Cocos2dactionsabout/Cocos2dactionsinstantactions/Cocos2dactions,Instantactionsintervalactions/Intervalactionscombining/Combiningactionscustomactions/Customactions

CocosNode/Gettingstartedwithcocos2dcollisiondetectiongame

about/BasiccollisiondetectiongameCollisionManagerinterface

about/ProcessingcollisionsCollisionManagerBruteForce/ProcessingcollisionsCollisionManagerGrid/Processingcollisions

colorsworkingwith/Workingwithcolors

componentbaseclass/Component-basedgameenginesCshape/Processingcollisionscustomactions

about/Customactions

Page 330: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Ddistanceformulation

CentOS,settingupTopicnURL/WorkingwithcolorsDomain-specificLanguage(DSL)/Thescenariodefinitiondraw_textmethod/AddingtheBreakoutitems

Page 331: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Eevadebehavior/Pursuitandevade

Page 332: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Ffaceculling

about/TheCubeclassenabling/Enablingfaceculling

fleebehavior/Seekandfleeframespersecond(FPS)/Pygame101freeglut/Installingpackagesfutureposition/Pursuitandevade

Page 333: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Ggame

starting/Startingthegamegameassets

creating/CreatinggameassetsGameclass/TheGameclassgamedesign

about/Anintroductiontogamedesignleveldesign/Leveldesignplatformerskills/Platformerskillscomponent-basedgameengines/Component-basedgameengines

gameframeworkbuilding/Buildingagameframeworkstart()method/Buildingagameframeworkupdate(dt)method/Buildingagameframeworkon_collide(other,contacts)method/Buildingagameframeworkstop()method/Buildingagameframeworkphysics,adding/Addingphysicsrenderablecomponents/RenderablecomponentsInputManagermodule/TheInputManagermoduleGameclass/TheGameclass

gamelayerbasicgameobjects,addingto/Thegamelayer

GameLayerclass/ThePlayerCannonandGameLayerclassesgameobjects

about/BasicgameobjectsBallclass/TheBallclassPaddleclass/ThePaddleclassBrickclass/TheBrickclass

GameObjects/Component-basedgameenginesgamescene

about/GamesceneHUDclass/TheHUDclassassembling/Assemblingthescene

glClear()function/DrawingshapesglClearColor()function/InitializingthewindowglColorfunctions/InitializingthewindowglLightfv()function/DrawingshapesglLoadIdentity()function/DrawingshapesglMaterialfv()function/DrawingshapesglMatrixMode()function/InitializingthewindowglPopMatrix()function/DrawingshapesglPushMatrix()function/Drawingshapes

Page 334: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

glTranslate()function/DrawingshapesgluLookAt()function/DrawingshapesgluPerspective()

function/InitializingthewindowglutDisplayFunc()function/InitializingthewindowGLUTlicensing/GettingstartedwithOpenGLglutMainLoop()function/InitializingthewindowglutSolidSphere()function/DrawingshapesglutSpecialFunc()function/Initializingthewindow,Processingtheuserinputgravitationgame

about/Gravitationgamebasicgameobjects/Basicgameobjectsplanets/Planetsandpickupspickups/Planetsandpickupsplayer/Playerandenemiesenemies/Playerandenemiesexplosions/Explosions

GUIapplicationbuilding/BuildingtheGUIapplicationwindow,creating/Creatingawindowandbindingeventsevents,binding/Creatingawindowandbindingeventsimages,layingout/CreatingandlayingoutimagesintheGUIimages,creating/CreatingandlayingoutimagesintheGUIcontrols,layingout/Creatingandlayingoutcontrolscontrols,creating/Creatingandlayingoutcontrolslayouts,nesting/Nestinglayoutsandsettingtherootlayoutrootlayout,setting/Nestinglayoutsandsettingtherootlayoutbackgroundthread,starting/Startingabackgroundthreadwindow,closing/Closingawindowandstoppingabackgroundthreadbackgroundthread,stopping/Closingawindowandstoppingabackgroundthreadanalyzerbasedonuserinput,configuring/Configuringtheanalyzerbasedonuserinputimages,updating/Updatingandshowingimagesimages,displaying/Updatingandshowingimagesrunning/Runningtheapplication

GUIlayout/ThebasicGUIlayout

Page 335: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Hhomography/Initializingtheentiremodelofthegame

Page 336: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Iinstallation

Python/InstallingPythoninstallation,NumPy/NumPyinstallationinstantactions

about/Cocos2dactions,InstantactionsPlace/InstantactionsCallFunc/InstantactionsCallFuncS/InstantactionsHide/InstantactionsShow/InstantactionsToggleVisibility/Instantactions

IntegratedDevelopmentEnvironments(IDE)about/Aquickdemonstration

intervalactionsabout/IntervalactionsLerp/IntervalactionsMoveTo/IntervalactionsMoveBy/IntervalactionsJumpTo/IntervalactionsJumpBy/IntervalactionsBezier/IntervalactionsBlink/IntervalactionsRotateTo/IntervalactionsRotateBy/IntervalactionsScaleTo/IntervalactionsScaleBy/IntervalactionsFadeOut/IntervalactionsFadeIn/IntervalactionsFadeTo/IntervalactionsDelay/IntervalactionsRandomDelay/Intervalactions

invadersabout/Invaders!Alienclass/Invaders!AlienColumnclass/Invaders!AlienGroup.class/Invaders!

iter_colliding(obj)method/Processingcollisions

Page 337: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Kk-meansclustering/Initializingtheentiremodelofthegameknownentities/Processingcollisionsknows(obj)method/Processingcollisions

Page 338: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Llayer/Cocos2dactionslearningcurve/Leveldesign

Page 339: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

MMac

settingup/MacMacPorts

URL/Macmainmenu

adding/Addingamainmenumathmodule

about/IntervalactionsURL/Intervalactions

Menu/Addingamainmenumove()method/AddingtheBreakoutitems

Page 340: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

NNumPy

installing/NumPyinstallationURL,forofficialbinaries/NumPyinstallationURL,forunofficialcompiledbinaries/NumPyinstallation

Page 341: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Oobstacleavoidancebehavior

about/ObstacleavoidanceOpenCV

settingup/SettingupOpenCVandotherdependenciesdependencies,settingup/SettingupOpenCVandotherdependenciesWindows,settingup/WindowsURL/WindowsMac,settingup/MacDebian,settingup/Debiananditsderivatives,includingRaspbian,Ubuntu,andLinuxMintRaspbian,settingup/Debiananditsderivatives,includingRaspbian,Ubuntu,andLinuxMintUbuntu,settingup/Debiananditsderivatives,includingRaspbian,Ubuntu,andLinuxMintLinuxMint,settingup/Debiananditsderivatives,includingRaspbian,Ubuntu,andLinuxMintFedora,settingup/Fedoraanditsderivatives,includingRHELandCentOSFedora,derivates/Fedoraanditsderivatives,includingRHELandCentOSRHEL,settingup/Fedoraanditsderivatives,includingRHELandCentOSCentOS,settingup/Fedoraanditsderivatives,includingRHELandCentOSOpenSUSE/OpenSUSEanditsderivativesOpenSUSE,derivates/OpenSUSEanditsderivativesmultipleversions,supporting/SupportingmultipleversionsofOpenCVreferences/FurtherreadingonOpenCV

OpenCVimagesconverting,forwxPython/ConvertingOpenCVimagesforwxPython

OpenGLabout/GettingstartedwithOpenGLwindow,initializing/Initializingthewindowshapes,drawing/Drawingshapesdemo,running/Runningthedemoprogram,refactoring/RefactoringourOpenGLprogramuserinput,processing/Processingtheuserinputused,fordrawing/DrawingwithOpenGLCubeclass/TheCubeclassfaceculling,enabling/Enablingfaceculling

OpenGLUtilityLibrary/InitializingthewindowOpenGLUtilityToolkit/GettingstartedwithOpenGLopticalflow/Detectingtheboard’scornersandtrackingtheirmotion

Page 342: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Ppackageinstallation

PyOpenGL/Installingpackagesfreeglut/InstallingpackagesPygame/Installingpackages

packagesinstalling/Installingpackages

Paddleclass/ThePaddleclassParticleSystemclass

about/TheParticleSystemclassdemonstrating/Aquickdemonstration

PlayerCannonclass/ThePlayerCannonandGameLayerclassesPlayerclass

about/ThePlayerclassanditscomponentsrespawn/ThePlayerclassanditscomponentsPlayerMovement/ThePlayerclassanditscomponents

projecttroubleshooting/Troubleshootingtheprojectinreal-worldconditions

pursuitbehavior/PursuitandevadePygame

about/InstallingpackagesURL/Installingpackages,Pygame101Macintosh,URL/Installingpackagesadding/AddingthePygamelibrary101/Pygame101documentation/Pygame101integration/Pygameintegration

Pygletabout/Installingcocos2deventframework/Handlinguserinput

pyglet.window.keymoduleURL/Handlinguserinput

Pymunkabout/IntroducingPymunk

pymunkpackage,classesspace/IntroducingPymunkbody/IntroducingPymunkshape/IntroducingPymunkarbiter/IntroducingPymunk

PyOpenGLabout/InstallingpackagesURL/Installingpackages,Initializingthewindow

PyPlatformer

Page 343: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

developing/DevelopingPyPlatformerplatforms,creating/Creatingtheplatformspickups,adding/Addingpickupsshootingaction/Shooting!Playerclass/ThePlayerclassanditscomponentsclass/ThePyPlatformerclass

PyPlatformerclass/ThePyPlatformerclasspyramidalLukas-Kanade/Detectingtheboard’scornersandtrackingtheirmotionPython

installing/InstallingPythonURL/InstallingPythonpackagingecosystem/Installingcocos2dspecialmethods/Combiningactions

Python2andTkinter/InstallingPython

PythonExtensionPackagesURL/Installingpackages

Pythonmodules,CheckersapplicationCheckers.py/PlanningtheCheckersapplicationCheckersModel.py/PlanningtheCheckersapplicationWxUtils.py/PlanningtheCheckersapplicationResizeUtils.py/PlanningtheCheckersapplicationColorUtils.py/PlanningtheCheckersapplicationCVBackwardCompat.py/PlanningtheCheckersapplication

PythonPackageIndex(PyPi)/Installingcocos2d

Page 344: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Rrender()method/Buildingagameframeworkrenderablecomponents

about/Renderablecomponentscameracomponent/TheCameracomponent

Page 345: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Sscenario

definition/Thescenariodefinitionscenarioclass/Thescenarioclassscenariomodule/Thescenarioclassscenes

about/Cocos2dactionstransitionsbetween/Transitionsbetweenscenesgameovercutscene/Gameovercutscene

seekbehavior/SeekandfleeSimpleDirectMediaLayer(SDL)/PygameintegrationSingle-boardComputer(SBC)/ConvertingOpenCVimagesforwxPythonslowingarea/ArrivalSpaceInvadersdesign

about/SpaceInvadersdesignPlayerCannonclass/SpaceInvadersdesign,ThePlayerCannonandGameLayerclassesAlienclass/SpaceInvadersdesignAlienColumnclass/SpaceInvadersdesignAlienGroupclass/SpaceInvadersdesignShootclass/SpaceInvadersdesignPlayerShootclass/SpaceInvadersdesignGameLayerclass/ThePlayerCannonandGameLayerclasses

sprite/Cocos2dactionsstartmethod

glutInit()/InitializingthewindowglutInitWindowPosition()/InitializingthewindowglutInitWindowsSize()/InitializingthewindowglutCreateWindow()/InitializingthewindowglEnable()/Initializingthewindow

start_gamemethod/Startingthegamesteeringbehaviors

implementing/Implementingsteeringbehaviorsseek/Seekandfleeflee/Seekandfleearrival/Arrivalpursuit/Pursuitandevadeevade/Pursuitandevadewander/Wanderobstacleavoidance/Obstacleavoidance

steeringbehaviors,forautonomouscharactersreferencelink/Implementingsteeringbehaviors

subclasses,MenuItem

Page 346: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

ToggleMenuItem/AddingamainmenuMultipleMenuItem/AddingamainmenuEntryMenuItem/AddingamainmenuImageMenuItem/AddingamainmenuColorMenuItem/Addingamainmenu

super(MyClass,self).__init__(arguments)/ThebasicGUIlayoutsyntacticsugar/Combiningactions

Page 347: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

TThreadBuildingBlocks(TBB)/MacTiledMapEditor

URL/TiledMapEditorabout/TiledMapEditor

tilemapsabout/TilemapsTiledMapEditor/TiledMapEditortiles,loading/Loadingtiles

TkinterURL/InstallingPythonandPython2/InstallingPython

TMXformat/TiledMapEditortowerdefenseactors

about/Thetowerdefenseactorsturret/Turretsandslotsslots/Turretsandslotsenemies/Enemiesbunker/Bunker

towerdefensegameplayabout/Thetowerdefensegameplay

Page 348: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Uupdate()method/Buildingagameframeworkupdatemethod/Movementandcollisionsupdate_lives_textmethod/AddingtheBreakoutitems

Page 349: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Vvalues,wanderbehavior

wander_angle/Wandercircle_distance/Wandercircle_radius/Wanderangle_change/Wander

Page 350: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

Wwanderbehavior

about/Wandervalues/Wander

WindowsURL/Windowssettingup/Windows

wxPythonURL/WindowsOpenCVimages,converting/ConvertingOpenCVimagesforwxPython

wxPythonPhoenixURL/Windows,Mac

Page 351: Python Game Programming By Example - Programmer Books · Benjamin Johnson is an experienced Python programmer with a passion for game programming, software development, and web design.

XXcodeCommandLineTools/Mac