3d platform tutorial

Post on 01-Nov-2014

3.335 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

3DPlatformerTutorialBuildinga3DPlatformGameinUnity2.0

Contents

1. IntroductionWhatyouwilllearn 5Whatyoushouldalreadyknow 6ProjectOrganization 6Files 7TypographicalConventions 7UnityConventions 8Projects 8GameObjects,Components,Assets&Prefabs 8GameObjects 8Components 9Assets 9Prefabs 9Acknowledgments 10

2. FirstStepsAnimatingLerpz 11ThePlot 11IntroducingLerpz 12TheCharacterController&theThirdPersonControllerscript 19AnimatingLerpz 19CharacterAnimation 20Animationblending 20TheThirdPersonPlayerAnimationscript 20Gizmos 21TheJet­Pack 22AddingtheParticleSystems 23AddingtheLight 26BlobShadows 29AddingaBlobShadow 30CreatinganewLayer 31ScriptingConcepts 33Organization&Structure 34Death&Rebirth 36

TheFalloutDeathscript 37RespawnPoints 38Howitworks 40

3. SettingtheSceneFirstSteps 42PlacingProps 43HealthPickups 43TheForceField 44ScriptingtheCollectableItems 45JumpPads 48

4. TheGUITheUserInterface 50Unity2'snewGUIsystem 50FurtherInformation 51TheIn­gameHUD 51TheGUISkinobject 52TheStartMenu 56SettingtheScene 57TheBackdrop. 58TheButtons. 60GameOver 64

5. AdversariesAntagonists&Conflict 69TheLaserTraps 69ImplementingtheLaserTraps 70TheLaserTrapScript 73Overview 73TheRobotGuards 75Divide&Conquer 77Spawning&Optimization 79Howitworks. 80

6. Audio&FinishingTouchesIntroduction 83Audio 83SampleNotes 84AddingSoundtoLerpzEscapes! 84AmbientSounds 86TheJumpPads 87Collectables 88TheImpoundFence 90

ThePlayer 90TheRobotGuards 94CutScenes 96Unlockingtheimpoundfence 96

7. OptimizingWhyOptimize? 110OptimizingRendering:MonitoringFramesPerSecond 110MakingsenseoftheStatsdisplay 111OptimizingRendering:TheTwo­CameraSystem 112

8. Endoftheroad.TheRoadLessTravelled 114SuggestedImprovements 114Fixingthedeliberatemistakes 114Morelevels 115Moreenemies 115Addscoring 115Addanetworkedhigh­scoresystem 115Addmultiplayersupport 115FurtherReading 115

9. ScriptAppendixStartMenuGUIscript 116GameOverGUI 117GameOverScript 118ThirdPersonStatus 118LevelStatus 120HandleSpaceshipCollision 122

Introduction

Withitsmyriadfeatures,includingheight­mappedterrains,nativenetworkingsup­port,completephysicsintegrationandscripting,Unitycanbedauntingfornewcom­ers,butmasteringitsmanytoolsisinfinitelyrewarding.

Thistutorialwillwalkyouthroughtheprocessofbuildingacomplete3Dplatformgamelevelwithathird­personperspectiveview.Thisincludeseverythingfromplayercontrols,collisiondetection,someadvancedscripting,blobshadows,basicAI,addingagameHUD,cut­scenesandaudiospoteffects.

WhatyouwilllearnThistutorialfocusesonthetechnicalsideofbuildingagameinUnity,coveringthefollowing:

• CharacterControllers

• Projectors

• AudioListeners,AudioSources&AudioClips

• MultipleCameras(andhowtoswitchbetweenthem)

• UnityGUIscriptingsystem

• Colliders

• Messages&events

Unityisapowerfultoolforgamedevelopment,suitableformanygamegenres,fromfirst­personshooterstopuzzlegames.

• Lighting

• Particlesystems

• Blobshadows

• Scripting(AI,statemachines,playercontrols)

Thistutorialwillshowhowthesefeaturescanbeusedtogethertocreateagame.

WhatyoushouldalreadyknowThistutorialmakesextensiveuseofscriptingsoyoushouldbefamiliarwithatleastoneofthesupportedscriptinglanguages:JavaScript,C#orBoo.(JavaScriptisusedforthescriptsinthistutorial.)

ItisalsoassumedthatyouarefamiliarwithUnity’sinterfaceandknowhowtoper­formbasicoperations,suchaspositioninganassetinascene,addingComponentstoaGameObject,andeditingpropertiesintheInspector.

ProjectOrganizationUnitydoesnotattempttoforceaparticularwayoforganizingyourproject'sassets.Youmaypreferorganizingyourassetsbyassettype,withseparatefoldersfor,say,"Textures","Models","Soundeffects"andsoon.AtUnityTechnologies,wehavefoundthisworkswellforsmallerprojects.Formorecomplexprojects,ourusersgen­erallyrecommendorganizingassetsbyfunction,perhapsgroupingthemunderfolders suchas"Player","Enemies","Props","Scenery",andsoon.

Thistutorial’sprojectwasworkedonbyanumberofteammembersandgreworgani­callytoreflecttheirdifferentconventionsandstyles.Intheinterestsofauthenticity,wehavedecidedtoleavetheproject'sorganizationasitwasasthisismorerepresen­tativeofa'smaller'project'sorganizationandstructure.

AbstractGameObjects&ComponentsUnity'sdesignplaceseachscene'sassetsatthecenterofthedevelopmentprocess.This makesforaveryvisualapproachtogamedevelopment,withmostoftheworkinvolv­ingdragginganddropping.Thisisidealforthebulkofleveldesignwork,butnotallassetscanbedisplayedinthisway.Someassetsareabstractratherthanvisualobjects,sotheyareeitherrepresentedbyabstracticonsandwireframegizmos­­e.g.AudioSourcesandLights­­orarenotdisplayedatallwithintheSceneView.Scriptsfallintothislattercategory.

ScriptsdefinehowassetsandGameObjectsinaUnitySceneinteractwitheachotherandthisinteractivityisatthecoreofallgames.Forthisreason,itisusuallyagoodplantokeepinformativenotesinsideyourscripts.

Thistutorialwillassumeyoucanreadtheprovidedscriptsandunderstandthemanycommentsliberallysprinkledthroughoutthem.However,whenaparticularscriptingtechniqueorconceptisimportant,wewillcoveritindetail.

6

Thescriptsaredocumentedthroughtheuseofextensivecommentsandhavealsobeendesignedtobeasself­explanatoryaspossibleintheirdesign.Weencourageyoutoreadthroughthescriptsasweintroducetheminthistutorial,studyingtheirwork­ings.Feelfreetoexperiment!

FilesThemostup­to­datefilesforthisprojectcanbedownloadedfrom:http://unity3d.com/support/resources/tutorials/3DPlatformProject.zip

The“Scenes”foldercontainsthefinalresult:amainmenuScene,agameoverSceneandaScenecontainingthecompletedgamelevel.

ThistutorialassumesyoualreadyknowbasicUnitycontrols,suchaspositioningob­jectsinascene,sothefirststartingpointScenealreadyhasthebasicsceneryandsomepropsinplace.

TypographicalConventionsThisisalongtutorialcontainingalotofinformation.Tomakeiteasiertofollow,somesimpleconventionsareused:

Background&TangentsTextinboxeslikethesecontainsadditionalinformationthatmayhelpclarifythemaintext.

Scriptingcodewillappearasshownbelow:

// This is some script code.Function Update(){ DoSomething();}

NOTE Thescriptsincludedinthetutorialincludeplentyofcommentsandarede­signedtobeeasytofollow.Thesecommentsareusuallyomittedinthecodefragmentsinthetutorialtexttosavespace.

ActionsyouneedtoperformwithinUnityareshownlikethis:

Clickonthis;

Thenthis;

ThenclickPlay.

7

Scriptnames,assets,menuitemsorInspectorPropertiesareshowninboldfacetext.Conversely,amonospace fontisusedforscriptfunctionsandeventnames,suchas

theUpdate()functioninthescriptexampleabove.

UnityConventionsUnityisauniquedevelopmentsystem.Mostdeveloperswillbeusedtoworkinginacodeeditor,spending90%oftheirtimeeditingcodeandevenwritingcodetoloadupanduseassets.Unityisdifferent:Itisasset­centricratherthancode­centric,placingthefocusontheassetsinmuchthesamewayasa3Dmodelingapplication.Forthisreason,itisworthunderstandingthekeyconventionsandterminologyuniquetoUnitydevelopment:

ProjectsAgamebuiltinUnitywillconsistofaProject.Thiscontainsallyourproject’selements,suchasmodels,scripts,levels,menus,etc.Usually,asingleProjectfilewillcontainalltheelementsforyourgame.WhenyoustartUnity2,thefirstthingitdoesisopenaProjectfile.(Ifyouhaveonlyjustinstalledit,thiswillbetheProjectfilecontainingtheIslandDemo.)

ScenesEachProjectcontainsoneormoredocumentscalledScenes.AsingleScenewillcon­tainasinglegamelevel,butmajoruser­interfaceelements,suchasgamemenus,game­oversequencesormajorcut­scenesmayalsoliveintheirownScenefiles.Com­plexgamesmayevenuseentireScenesjustforinitializationpurposes.ThusalllevelsinagamewillmostlikelybeScenes,butnoteveryScenewillnecessarilybeagamelevel.

GameObjects,Components,Assets&PrefabsKeytounderstandingUnityistherelationshipbetweenaGameObjectandaCompo­nent.

GameObjectsAGameObjectisthefundamentalbuildingblockinUnity.AGameObjectisacon­tainerfordifferentpiecesoffunctionalitycalledComponents.AGameObjectalmostalwayscontainsmorethanoneComponent.AllGameObjectscontainaTransformComponent,whichdefinesitspositionandorientation.

GameObjectHierarchiesTherealpoweroftheGameObjectisitsabilitytocontainotherGameObjects,actingmuchlikeafolderinOSX’sFinder.ThisallowshierarchicalorganizationofGameOb­jects,soacomplexmodeloracompletelightingrigcanbedefinedunderasingleparentGameObject.(Infact,mostmodelswillappearinUnityasahierarchyofGameObjectsbecausethisreflectshowtheyaredefinedinthemodelingpackage.)AGameObjectdefinedinsideanotherGameObjectisconsideredachildGameObject.

8

ComponentsComponentsarethebuildingblocksofGameObjects.Withoutthem,theGameObjectwon’tdoanythinginteresting.

AComponentmayrepresentvisibleentities,suchasmeshes,materials,terraindataoraparticlesystem.OtherComponenttypesaremoreabstract,suchasCamerasandLights,whichdonothaveaphysicalmodelrepresentingthem;instead,youwillseeaniconandsomewire­frameguidelinesillustratingtheirkeysettings.

AComponentisalwaysattachedtoaGameObject;itcannotlivealone.MultipleCom­ponentscanbeattachedtothesameGameObject.GameObjectscansupportmultipleComponentsofcertaintypes―aGameObjectcancontainanynumberofscripts,forexample.Butothers,suchasthoseusedtodefineparticlesystems,areexclusiveandcanonlyappearonceinanysingleGameObject.Forexample,ifyouwanttodefinemultipleparticlesystems,youwouldnormallyuseahierarchyofGameObjects,eachcontainingitsownsetofparticlesystemComponents.

AssetsAllyourimportedAssetsappearintheProjectPaneandtheycanbealmostanything:asimplematerialortexture,audiofiles,orevenacomplete,prefabricatedGameOb­ject(knownasa“Prefab”).

Forexample,aplayercharacterPrefabcouldbedefinedasasingleAsset,containingthemodelanditsassociatedanimations.ItcouldalsocontainscriptComponents,audioclipsandanyotherComponentsitneedstofunction,soyoucouldsimplydragitintoaSceneandinstantlyhaveafullyoperationalavatar.

CustomIcons&GizmosYoucantellUnitytodisplaycustomiconsandothervisualinformationforyourAssetsifyouwish.Wewillseeanexampleofthisinthenextchapter.

Yourproject’sAssetsareshownintheProjectPane.WhenyoudroponeintoyourScene,itappearsintheHierarchyPane,whichdefinesthecontentoftheScene.(ASceneistheequivalentofthestageinatheater.Itcanbealevel,amenu,amulti­playergamelobby­­whateveryouwish.)TheProjectPaneisretainedacrossallScenes inyourProject.

PrefabsAPrefabisanAssetwhichhasbeendefinedasatemplate.ItistoUnitywhatatem­platedocumentistoawordprocessingapplication.WhenyouplaceaPrefabintoyourScene,UnityplacesalinktothePrefabintotheHierarchyPane,notacompletecopy.Thisiscalledinstantiation.EachlinkyoumakeisreferredtoasaninstanceofthePrefab.

IfyouclickonaPrefabinyourProjectPaneandtweakitssettings,youwillfindthatthosechangesareinstantlyreflectedinalltheinstancesinyourScene.ThismakesPre­

9

fabsidealformanyre­usableelements,suchasbullets,enemiesandsoon.Ifyoufindyourenemyisn’tbehavingcorrectly,youonlyneedtoadjustthescriptorsettingsintheoriginalPrefabinsteadofeditingeachoneintheSceneindividually.

However,ifyouneedtoadjustacoupleofsettingsinaspecificinstanceofaPrefab,youmaydothistoo:thesechangeswillonlyaffectthatparticularinstance.

PrefabsaredisplayedinbluetextinboththeProjectandHierarchyPane.

NOTE APrefabinstancecannothaveadditionalComponentsaddedtoitasdoingsowillbreakthelinktotheoriginalPrefab.Unitywillwarnyouifyoutryanddothis.Unitywill,however,allowyoutoupdatetheoriginalPrefabwithsuchchangesafterthelinkisbroken.

AcknowledgmentsThistutorialcouldnothavebeenproducedwithoutthefollowingpeople:

DavidHelgason,JoachimAnte,TomHiggins,SamKalman,KeliHlodversson,NicholasFrancis,ArasPranckevičius,ForestJohnsonand,ofcourse,EthanVosburghwhopro­ducedthebeautifulassetsforthistutorial.

10

FirstSteps

AnimatingLerpzInthischapterwewilllookat:

• Implementingthird­personplayerandcameracontrols

• Controllingandblendinganimations

• Usingparticlesystemstoimplementthejet­pack’sthrusters

• Addingablob­shadowtotheplayer

• Maintainingtheplayer’sstate

• Handlingplayerhealth,deathandre­birth.

Beforewecanbegin,weneedtoknowwhatthisgameisallabout.Inshort,weneed...

ThePlotOurheroisLerpz:analienvisitingRobotWorldVersion2.ThisreplacedRobotWorldVersion1,whichsufferedaparticularlybrutalsegmentationfaultandabruptlycrashedintoitssunmanyyearsago.

Unfortunately,Lerpzhashadsomebadluck:hisspaceshiphasbeenimpoundedbythecorruptlocalpolice.Afterlookinghighandlow,Lerpzhasfoundhisspaceship,but

Everyplatformgamehasitsstarcharacterwhotheplayercontrols.OurstarisLerpz.

howcanhegetitbackfromMr.Big’snastier,obsessive­compulsivecousin,Mr.EvenBigger?

Mr.Biggerlovesnothingmorethanartisticallyarrangingfuelcanistersonhisfloatingpatio.Heparticularlyadmireshowtheyglowwhenheplacesthemonhoverpads.(And,ofcourse,they’recheaperthanfittingnormalgardenlights.)

Butthere’ssomethingMr.Biggerhasn’trealized!Thankstohispenny­pinchingways,Lerpzknowsthatifhecollectsallthefuelcanisters,thepowerusedtokeepthemhoveringwilloverloadthesecuritysystem.Thiswillshutdowntheimpoundlot’sfenceandfreeLerpz’sspaceship.Lerpzcanthenenterhisspaceship,addthefuelfromthecansandflyawaytofreedom.

Allourherohastodoiscollectenoughfuelcanistersandtheimpound’sforcefieldwillautomaticallyshutdown.Lerpzcanthengetbackintohisspacecaranddriveitaway.Mr.Bigger’shiredrobotguardswilltrytostopLerpz,butluckily,they’renotparticularlybright.

Nowthatthat’soutoftheway,wecanstartfleshingoutourhero.

IntroducingLerpzOpentheprojectupandviewtheScenes­>TheGameScene.

OurfirststepistoaddLerpztoourScene:

OpentheObjectsfolderintheProjectPane;

DragtheLerpzPrefabintoeithertheSceneViewortheHierarchyView;

ClickonthenewLerpzentryintheHierarchyandrenameittoPlayer;

KeepthePlayerobjectselected,movethemouseovertheSceneViewandtaptheF(focus)keytocentertheviewontheLerpzmodel.

MoveLerpzontotheraisedplatformwiththeJumpPad(thenichewiththeyel­lowchevrons),neartheJail.(Seethescreenshotonthenextpage.)

IfyouclickPlaynow,youshouldseeLerpzstandinginthecourtyardoutsidethejail.Atthisstage,Lerpzcannotbemovedandthecameraalsoneedstobelinkedtoourplayer'scharacter.

ClickthePlaybuttonagaintostopthegame.

WeneedtogetLerpzmoving,butfirst,weneedtostepbackamomentandtakealookatourcamera.

12

PositioningLerpzintheScene.

ThirdPersonCameras.Inafirst­personshooter,thecameraistheplayer'spointofview,sothereisnoneedtoworryaboutmakingitfollowanotherobjectaroundthescene.Theplayercontrolsthecameraobjectdirectly.First­personcamerasarethereforerelativelyeasytoim­plement.

However,athird­personviewpointcamerarequiresacamerathatcanfollowtheplayeraround.Thisseemssimpleenoughuntilyourealizethecameraalsoneedstoavoidgettingscenerybetweentheplayer'scharacterandthecamera'sviewpoint.Thiscanbeachievedusingraycastingtocheckforunwantedobjectsbetweenthecameraandplayeravatar,buttherearesomespecialcasestoconsider.Forexample:

• WhathappensifLerpzisbackedupagainstasolidwall?Shouldthecameramoveaboveandlookdownontheplayer?Shoulditmovetotheside?

• Whatifanenemygetsbetweenthecameraandourplayeravatar?

• Howshouldtheplayercontrolswork?Shouldtheyberelativetothecamera'sview?Ifso,thiscouldgetveryconfusingifthecameramovesinanunexpecteddirectiontoavoidanobstacle.

Anumberofsolutionsforthird­personcamerashavebeentriedovertheyears.It'sarguablethatnonehaveeverbeen100%perfect.Somesolutionsfadeoutanythingbetweenthemandtheirfocus,makingwallsorenemiessemi­transparent.Otherop­tionsincludecameraswhichfollowtheplayeraround,butwhichwill,ifnecessary,movethroughwallsandbuildingstokeeptheplayer'sviewconsistent.

13

Theprojectsuppliedwiththistutorialincludesafewdifferentcamerascripts,butforthepurposesofthistutorial,we'lluseSpringFollowCamera.You'llfinditinthePro­jectPaneinsidetheCamerasub­folderoftheScriptsfolder.

DragtheSpringFollowCamerascriptfromtheProjectPaneontotheNearCam­eraobjectintheHierarchyPane.

ClickPlay.

Youwillgetanerrormessage.ThisappearsjusttotherightofthePlay,PauseandStepbuttonsatthebottomofUnity’swindow.

BringuptheDebugConsole(Shift+Cmd+C/Shift+Ctrl+ConPC),ifitisnotal­readyvisible.

Thisdisplaysanywarnings,errorsandotherdebugginginformationfromyourgame.Youwillprobablyseealotofcopiesoftheerrormessagerepeatedinthelog.High­lightoneandthepanebelowthelogwillshowabitmoreinformationaboutthiser­rormessage,asshowninimage3.1.

“Notarget”errormessage.

14

TIP Wheneverpossible,theDebugLogwindowwillshowalinelinkingtotheof­fendingGameObjectintheHierarchy,(ortotheProjectPaneifthefaultisinaPrefaborScript).Youcanseethislineinthescreenshotabove.

TheUnassignedReferenceExceptionerrortypeisoneyouwilllikelyseeveryfrequentlyifyou’renewtoUnity.Itsoundsscary,butallitmeansisthatascriptvari­ablehasnotbeenset.TheDebugLogexplainsthistoo,solet’sdoasitsuggests:

ClickontheNearCameraobjectintheHierarchyPaneandlookattheSpringFollowCamera(Script)Component’sproperties.

TheTargetpropertyissettoNone(Transform).Thisdefinesthetargetobjectwewantthecameratopointat,solet’ssetit:

Stopthegameifyouhaven’tdonesoalready.

Ifit’snotalreadyselected,clickontheNearCameraobjectintheHierarchyPane.

DragourPlayerGameObjectfromtheHierarchyPaneontotheTargetsettingtosetit.

MakingChangesWhilePlayingWhenyouareplayingthegame,Unitywillletyoutweakthepropertiesofthevarious gameobjectsandcomponentsinthegame.However,itwillnotsavethem!Themo­mentyoustopthegame,anychangeswillbediscarded!

Ifyouwantyourchangestostick,alwaysstopthegamefirst!

IfyouclickPlaynow,thecamerastillwon'twork.YouwillseeaerrorsrelatingtotheSpringFollowCamerascript.ItneedsthetargettohaveaThirdPersonControllerscriptattachedtoit.Thisisbecauseathird­personcameraiscloselytiedtotheplayercon­trols:itneedstoknowwhattheplayerisdoingsothatitcanreactaccordingly.

Thefinalsettingsshouldlookasshownintheimagebelow:

15

SpringFollowCamerascriptsettings.

Experimentwiththenumbersifyoudon'tlikethewaythecameraworks;thisisasub­jectivejudgementandthereisnosinglecorrectsettingforsomethinglikethis.

Thisisthefirstinaseriesofdependenciesthatweneedtodealwith.

CompletetheconnectionbetweenthecameraandtheplayerbydraggingtheThirdPersonControllerscriptfromtheScripts­>PlayerfolderintheProjectPaneontoourPlayerGameObject(intheHierarchyPane).(ThiswillbreakthePrefabconnection.)

TheThirdPersonControllerscriptalsohasitsownrequirementsanddependencies.ThemostimportantoftheseistheCharacterControllercomponent.Luckily,thescriptalreadytellsUnityaboutthis,soUnitywilladdthiscomponentforus.

Connections&Dependencies.Unityexcelsatshowingvisualassets,butthesealsohavetobeconnectedtoeachothertoprovidetheinteractivityweexpectfromagame.Theseconnectionsarediffi­culttoshowvisually.

Theseconnectionsareknownasdependencies,andit'swhatyougetwhenoneobjectrequiresasecondobjecttofunction.Thatsecondobjectmay,inturn,requireyetmore

16

objectstowork.Theresultisthatyourassetsaretiedtoeachotherwithmyriadvir­tualbitsofstring­­scripts­­tyingthemalltogethertomakeagame.

Definingallthesedependenciesisakeyelementofgamedesign.

WenowneedtoaddatagtoourPlayerGameObject.ThisissothatscriptscanfindourPlayerintheScenebysimplytellingUnitytofindtheGameObjectwithsaidtag.

WithourPlayerobjectdisplayedintheInspector,opentheTagdrop­downmenuandchoosethe“Player”tag,asshownbelow.

SelectingthePlayertag.

NOTE TheTagslistedinthemenushownaboveareprovidedbyUnitybydefault.We’lllearnhowtocreateourownTagsandLayerslater.

TheTagwillbeusedlater,solet’sreturntotheCharacterControllerandourscript.

SelectthePlayerobjectandlookintheInspector.Itshouldlooksimilartothis:

17

CharacterControllerandThirdPersonControllerScriptComponentsinplace.

OurnextstepistoadjusttheCharacterController.Atthemoment,theCapsuleCol­lideritusesislocatedtoofardownintheYaxis,soLerpzstandsonthinair.(YoucanseetheCollider'spositionintheSceneView:it'sthelongbluecylindricalwireframeshape.)WeneedtochangetheCenterYvalue.

AdjustingtheCharacterController’sCapsuleCollider­­displayedasabluewireframe.

18

PositiontheCapsuleColliderasshowninthescreenshotabove.(Alittleexperi­mentationsuggestssettingtheCharacterController’sCenterYto1.03willalignitslowerendperfectlywithLerpz'sfeet.)

IfyouclickPlaynow,Lerpzshouldnowmovearoundwhenyouusethecontrolkeyswithhisfeetfirmlyontheground.

TheCharacterController&theThirdPersonControllerscriptInmostgames,theplayer’savatariscapableofimpossiblephysicalfeatssuchasturn­ingandstoppinginstantaneously,leapingimprobabledistancesandotheractionswhichwouldbedifficulttomodelusingtraditionalphysics.TheCharacterControllerthereforedecouplesourplayeravatarfromthephysicsengine,providingbasicmove­mentcontrols.

TheCharacterControllersimplifiesmovementforaplayer(andmanynon­player)charactertypes.Itconsistsofacapsulecollidertiedtoabasicmovementsystem,al­lowingourcharactertomovearound,climbstepsandslideupordownslopes.YoucanchangethemaximumstepandslopesizesintheInspector.

TheCharacterControllerisnormallyusedwithascript.ThistalkstotheCharacterControllerandextendsitscapabilitiestomeettheneedsofthegame.Inourproject,theThirdPersonControllerscriptperformsthisfunctionandaddsthenecessarysup­portforourplatformgame.Itreadsthejoystick,keyboard,mouseorotherinputde­viceandactsuponittocontroltheplayer’savatar.

TheUnityInputManager(Edit­>ProjectSettings­>InputManager)allowsyoutodefinehowtheinputdevicescontroltheplayer.

NOTE Thereisnothingspecialaboutthescriptsweareusingfortheplayer.TheyareperfectlyordinaryUnityscriptswhichhavebeenbuiltforthisproject.ThereisnodefaultCharacterControllerscript.

TheThirdPersonControllerscriptisalreadypartofthePrefab,sothereisnoneedtoaddit.

ThenextstepistomakeLerpzanimatecorrectlyandaddtheadditionalmovements,suchasjumpingandpunching...

AnimatingLerpzAtthispoint,Lerpzisjustglidingacrossthescenery.ThisisbecausetheCharacterCon­trollerdoesn'thandleanimation.Itdoesn'tknowanythingaboutourplayer'smodelorwhichanimationsequencesapplytoeachmovement.WeneedtoconnectLerpztohisanimationsequencesandthisisdonewiththeThirdPersonPlayerAnimationscript.

UsetheComponentmenutoaddtheThirdPersonPlayerAnimationscripttothePlayergameobject.

IfyouclickPlaynow,you'llseeLerpzanimatingcorrectly.

19

Sowhat’sgoingonhere?Whatdoesthisscriptdo?TheanswerliesinhowUnityhan­dlescharacteranimationdata.

CharacterAnimationCharacteranimationsequencesarecreatedwithinamodelingpackage,like3DStudioMax,Maya,BlenderorCheetah3D,.OnimportingintoUnity,thesesequencesareautomaticallyextractedandstoredinanAnimationcomponent.

Theseanimationsequencesaredefinedonavirtualskeleton,whichisusedtoanimatethebasicmodel.Theseskeletonsdefinehowthemodel'smesh­­theimportantdatadefiningthevisiblesurfacesofthemodelitself­­ismodifiedandtransformedbytheenginetoproducetherequiredanimation.

Skeletons&ArmaturesIfyouarefamiliarwithstop­motionor“claymation”animationtechniques,youmaybeawareoftheiruseofmetalarmatures.Theanimatedmodelsarebuiltaroundthesearmatures.Thevirtualskeletonsusedin3Dmodelsaredirectlyequivalenttotheseandarerarelyascomplexasrealskeletons.

Themeshcomponentofsuchmodelsiscommonlyreferredtoasaskinnedmesh.Thevirtualskeletonprovidesthebonesbeneaththemeshanddefinehowitanimates.

AnimationblendingCharacteranimationsareusuallyblendedtogethertoprovidethenecessaryflexibilityforagame.Forinstance,ananimatedwalkcyclecouldbeblendedwithaseriesofspeechanimations,theresultbeingacharacterthatiswalkingandtalkingatthesametime.

Blendingisalsousedtoproducesmoothtransitionsbetweenanimations,suchasthetransitionbetweenawalkcycleandapunchsequence.

WeneedtouseascripttotellUnitywhenweneedtoswitchanimations,whenani­mationblendingisneededandhowitshouldbedone.Thisiswherescriptingcomesin.

TheThirdPersonPlayerAnimationscriptTheLerpzmodelwe'reusingwascreatedformultipleprojectsandcontainsfifteenanimationsequences.Onlyelevenareusedinthistutorial.IfyouselectthePlayerob­jectintheHierarchypaneandlookattheInspector,youwillseeallfifteenanimationsequenceslistedwithintheAnimationcomponent,ofwhichonlythefollowingareactuallyusedinthistutorial:

• Walk­­Thenormalwalkcycle.

20

• Run­­Arunninganimation.(HoldtheShiftkeywhileplayingtorun.)

• Punch­­Playedwhenattackinganenemyrobotguard.

• Jump­­PlayedwhenLerpzleapsintotheair.

• Jumpfall­­PlayedwhenLerpz'sleapreachesitsapexandhestartstofall.

• Idle­­AloopplayedwhenLerpzisidle.

• Walljump­­AbackflipanimationplayedwhenLerpzjumpsoffawall.

• Jet­packJump­­PlayedwhenLerpz'sjet­packisslowinghisfall.

• Ledgefall­­PlayedwhenLerpzstepsofftheedgeofaplatform.

• Buttstomp­­PlayedwhenLerpzhasbeenstruckbyarobotguard.

• Jumpland­­PlayedwhenLerpzlandsafterajumporfall.

ThemodelandanimationsforLerpzwerecreatedusingMayaandimportedintoUnity.Formoreinformationaboutimportingmeshesandanimations,pleaserefertotheUnityManual.

MostoftheseanimationsaredealtwithbytheThirdPersonPlayerAnimationscript,whichchecksthecontrolstheplayerisusingandreactsaccordingly.Someanimationsarelayeredoverotherswhileothersaresimplyqueueduponeafteranother.Thescriptismostlyasetofmessageresponderfunctions.TherelevantmessagesarefiredoffbytheThirdPersonControllerscript,whichreadstheinputdevicesandupdatesthecharacter’sstateaccordingly.

Lerpz'sattackingmove­­hispunch­­isdealtwithinaseparatescript,ThirdPerson­CharacterAttack.(We’lladdthisscriptlater.)Thismayseemanarbitrarysplit,butitisnot:mostofthebasicmovements­­walking,running,jumping,etc.­­areprettysimi­larnomatterwhatyourplayer'scharacterlookslike.However,attackinganddefen­sivemovestendtobemuchmorevaried.Insomeplatformgames,theplayercharac­termighthaveagun;inanother,hemightperformamartialartsmove.

Inthistutorial,LerpzisamasterofthepopularWesternmartialartknownasFisti­cuffs,anamewhichtranslatesto"hittingyouropponentveryhardwithyourfist".Theanimationisasimplepunchinganimation.

GizmosThirdPersonCharacterAttackalsoincludesausefultestingfeature:agizmowhichdrawsaspheretorepresenttheareaaffectedbyLerpz'spunchingaction.GizmosaredrawninsideoneoftwoGizmo­drawingmessagehandlingfunctions.Inthisexample,thegizmo­­wireframeyellowspheredrawnatthepunchpositionanddisplayingitsareaofeffect­­isdrawninresponsetotheOnDrawGizmosSelected()function.

21

ThisfunctionmustbestaticandwillbecalledbytheUnityEditoritself.AnalternativeisOnDrawGizmos(),whichiscalledbytheUnityEditoreveryupdatecycle,regard­lessofwhethertheparentGameObjecthasbeenselected.

TheJet­Pack

Lerpz’sJet­packinaction.

Atthispoint,ourcharacterisrunningandjumpingaround,buthisjet­packisnotyetworking.Lerpzusesthejet­packtoslowhisrateofdescent.Themovementisalreadyinplace,butthejet­pack'sjetsdon'tanimateatall.Tomakethejetswork,wewillneedtoaddtwoParticleSystemsandaPointLightcomponent.Theparticlesystemswillproduceaflame­likeeffect,whilethepointlightwillgivetheillusionoftheflamesactingasasourceofillumination.

TIP Ideallywewouldhaveapointlightsourceforeachjet,butthejetexhaustsarecloseenoughtoeachotherthatwecangetawaywithjusttheone.Aslightsarecomputationallyexpensive,thisisahandyoptimization.

WhatisaParticleSystem?ParticleSystemsemitdozensofparticles­­usuallyflat2Dbillboardsorsprites­­intothe3Dworld.Eachparticleisemittedatasetspeedandvelocity,andlivesforacer­taintime.Dependingonthesettingsandbillboardmaterialsused,theseparticlesys­temscanbeusedtosimulateanythingfromfire,smokeandexplosionstostar­fields.

22

AddingtheParticleSystemsUsetheGameObjectMenutocreateanemptyGameObjectintheHierarchyPane.

RenamethisGameObject"Jet".

WiththenewGameObjectselected,add:

AnEllipsoidParticleEmitter

AParticleAnimator

AWorldParticleCollider

AParticleRenderer

Uncheckthe“Enabled”checkboxintheInspectorfortheParticleRendererComponent.Thiswilldisableittemporarily.

PositiontheJetdirectlybelowLerpz'sright­handjetexhaust.

Re­enabletheParticleRenderer.

AdjustthesettingsfortheEllipsoidParticleEmitterasshownbelow:

EllipsoidParticleEmittersettings.

23

Thesesettingsresultinanarrowstreamofparticleswhichwewillusetosimulateajetofflame.

TIP Iftheparticlesarenotmovingdirectlydownwards,useUnity'srotationtoolstorotateourobjectuntilthejetismovinginlinewiththejet­pack'sexhaust.

Whenwe'redone,theparticlesystemwillbeattachedtothePlayer's"torso"childobjectinitshierarchy.Thiswillcausethejettofollowtheplayer'smovements.Atthispointhowever,we'reprimarilyinterestedingettingittolookright,sodon'tworrytoomuchaboutaccurateplacement.

TheMinSizeandMaxSizesettingsdefinethesizerangeoftheparticles.TheMinEn­ergyandMaxEnergysettingsdefinetheminimumandmaximumlifespanofthepar­ticles.Ourparticleswillliveforonlyashorttime­­0.2secondsinthiscase­­beforefadingaway.

Wesetthequantityofparticlestoemitto50.TheMinEmissionandMaxEmissionrangedefineshowmanyparticleswewantonscreenatanyonetime,andhowvari­ablethisemissionshouldbe.Wehavechosentosettheminimumandmaximumrangetosimilarvaluesorit'lllooklikethejetissputteringratherthanblastingawaysmoothly.Theresultshouldbeasmoothflowofparticles.

NOTE We'vedisabled"SimulateinWorldspace"here.Thishelpsgivetheimpressionthatwehaveahot,fastjetofgasratherthanamuchslowerflame,eventhoughtheparticlesaren'tmovingallthatquickly.BymakingthejetignoreLerpz'stwistsandturns,itlookssimilartothehot,steadyflamefromablow­torch.

Now,settheParticleAnimatorComponent’ssettingsasshown:

ParticleAnimatorsettings.ThevaluesinthechartdefinetheColorAnimationentries.Don’tforgettoadjusttheothersettingstoo!

24

Color Red Green Blue Opacity

Animation(0)

Animation(1)

Animation(2)

Animation(3)

Animation(4)

254 255 219 100

254 255 116 29

255 92 0 13

105 63 50 10

13 15 17 4

TheParticleAnimatorwillanimatetheparticlecolorsastheyage.Theparticleswillstartoffwhite,darkeningthroughyellowandorangeasourvirtualjetcools.Sincewe'regoingtoberenderingatextureintoeachparticle,theParticleAnimatorwillbeusedtotintthisparticle,sothecoloranimationwillbesubtle,buthopefullyeffective.

Thecolorpickerdialogwhichappearswhenyouclickonacoloralsooffersan“Opac­ity”slider.Thetableshownwiththecolorsettingsalsoincludethisas,byreducingopacitythroughtheanimationcycle,theparticle'flames'willappeartofadeawayastheycool.

NOTE TheOpacitysettingmodifiestheAlphachanneloftheparticle,defininghowtransparentitis.Theparticlematerialweusealreadyincludesalphachannelinformation,whichisthenmodifiedfurtherbytheOpacitysetting.

NextistheParticleRenderer.ThisComponentdrawseachparticle,soitneedstobetoldhowtheparticleswillappear.Italsodefinesthematerialtousetorendereachparticle.Wewantaflame­likejeteffect,soweshallusethe“fireadd”material,whichcanbefoundin:Particles­>Sources­>Materials­>fireadd

TIP ThisassetisalsoincludedintheStandardAssetsfolder.

Setthiscomponent'svaluesasfollows:

25

ParticleRenderersettings.

TheStretchParticlessettingtellsUnitywhethertheparticlesshouldberenderedstretchediftheyaremovingathighspeed.Wewanttheparticlestostretchalittleaccordingtotheirvelocity.Thisaddsasubtlevisualcueandmakesthesmall,roundshapeswe'reusingforthisjetblendmoreintoeachother.

NOTE TheCastShadowsandReceiveShadowssettingshavenoeffectunlessyouuseacustomshader.Thisisanadvancedtopicbeyondthescopeofthistutorial.

AddingtheLightOurjetlookscool,butit’sreallyanillusion:ParticleSystemsjustspitoutlotsoftinyimages,buttheresultingflame­likeeffectdoesnotemitlight.Tocompletetheillu­sion,wewillcreateaseparatePointLightGameObject.We’llswitchitonandoffatthesametimeasthejets.Theresultwillbeajetofflamewhichappearstolightupitsimmediatesurroundings.(Wewillonlyuseasinglelight,ratherthanonelightperjet;thissavesprocessingpowerwhilemaintainingourillusion.)

CreateanewPointLightGameObject.

26

Namethis"JetLight"andpositionitbetweenthetwomodeledjetsonLerpz'sjetpack.(We’llcomebacktoourJetparticlesystemshortly.)Thislightwillcre­ateanillusionthatthejetsareemittinglight.

Forthiseffecttowork,weneedabrightpointlightwithahighintensity.

SelecttheLightandadjustsettingsasshown:

Jet­packLightsettings.

Whynoshadows?Shadowsarecomputationallyexpensiveformosthardware.Itmakessensetoavoidcalculatingthemifwecanavoidit,andthisisoneareawherewecangetawaywithit.Thejetsaren'tverybig,sotheyonlyneedtolightupLerpz'sback.Thepointlightwillalsobereflectedinnearbyscenery,butitwon'tbebrightenoughtomakethelackofshadowsnoticeable.

ThenextstepistoupdatethePlayerGameObjecttoincludeourjetandlightobjects.Todothis,we’llfirstaddourJettotheProjectPaneasaPrefab:

IntheProjectPane,selectthe(empty)Playerfolder,thenclickCreate...

Fromthedrop­downmenu,choosePrefab.ThiswillcreateanemptyPrefabob­jectinside.

RenametheemptyPrefabtoJet.

DragourshinynewJetobjectfromtheHierarchyPaneontoournewPrefab.

TheJet’snameintheHierarchyPaneshouldturnbluetoshowthatit’snowlinkedtoaPrefab.We’regoingtousetwoinstancesofourJetPrefabforLerpz’sjetpack.UsingaPrefabmeanswecantweakboththejetsbysimplyeditingtheoriginalPrefab.

27

Color Red Green Blue Opacity

Color 255 206 155 100

DeleteouroriginalJetobjectfromtheHierarchyPane.(ButleaveourJetLightwhereitis!)

Nowweaddthejet(twice)andthelight(once)toourPlayer:

GotothePlayerobjectintheHierarchy,

Openitupuntilyoufindthetorsochildobject.

DragtheJetPrefabobjectontothisobjecttwice.ThiswillcreatetwoJetin­stances.

RenamethetwoJetinstancesJetLandJetR,

DroptheJetLightontothesametorsoobject.

Youshouldnowhaveanobjecthierarchythatlookssomethinglikethis:

Jet­packHierarchy.

UseUnity'smanipulationtoolstopositioneachJetPrefabunderitsrespectivejetoutletinLerpz'smodel.Youmayneedtorotatethem,sothattheparticlesaregoingintherightdirection.

MovetheJetLighttoapointbetweenthetwoJetPrefabs.

Whenyou'veachievedthis,Lerpzshouldnowhavetwoflamingjetsgushingfromhisjet­packashemovesaround.We'realmostdone!

ThefinalstepistomakeJetPrefabsandJetLightobjectsactivateonlywhenhe'sjumping.Thisisachievedthroughscripting.

LookfortheJetPackParticleControllerscriptinScripts­>Playeranddragthisontothetop­most"Player"objectintheHierarchyPane.Thisaddsthescripttoourplayercharacter.

28

Youshouldnowfindthatthejet­packworksasexpected.Thescriptcontrolsthetwoparticlesystemsandthelight,synchronizingthemwithLerpz’smovementsandtrig­geringallthreeelementstogetherwhenevertheplayerpressesthejumpbuttontojumportoslowhisdescent.

Lerpz’sJet­packinaction.

BlobShadowsLerpzmustbeeasytoidentifyatalltimes,sothatplayerswon’tlosetrackoftheiravatarswhenthegamegetsvisuallybusy.Mostofthisworkisuptotheartistsandthegame’sdesigner,buttherearesomeelementswhichhavetobehandledbyUnityitself.

Oneofthemostimportantoftheseisshadowingandlighting.Toaidperformance,theeffectsoflightingareoftenpre­renderedintothetexturesbytheartistusingamodelingpackage­­atechniqueknownas“baking”.Thistechniqueonlyworkswellonstaticobjects,suchassetsandfixedprops.(Wehavedeliberatelyavoidedthistechniqueforthistutorial’sassets.)Acharacterwalkingunderastreetlightneedstoreacttothatlightinrealtime.Theroadbeneaththecharactercanhavethelightingbakedin,butthecharactercannotusethistrickandalsoneedstoreacttothelights.

Thesolutionistopositiondynamiclightswheretheyneedtobe­­ifyouuse”baked”textures,remembertoaddalightwhereveroneisimpliedbythebakedlighting­­butmakethelightsonlyaffectmovingobjects.Lightshavealreadybeenplacedinthesceneforyou.

Thisleavesonefinalelement:shadows.

29

Ina3Dplatformgame,theshadowplaysakeyroleintellinguswherethecharacterwilllandifheisjumpingorfalling.ThismeansLerpzshouldhaveagood,visibleshadow,whichisnotthecaseatthemoment.

Shadowscanbeproducedusinglights,withtheshadowcomputedandrenderedinrealtimebythegraphicsengine.However,suchshadowsareexpensiveintermsofprocessingpower.Inaddition,notallgraphicscardscancomputeshadowsquicklyoreffectively;oldercardsmaynotbeabletodosoatall.

Forthisreason,wewilluseaBlobShadowforLerpz.

AddingaBlobShadowABlobShadowisacheat.Insteadofcastingraysoflightandcheckingiftheyhitany­thing,wesimplyprojectadarkimage­­inthiscasejustacircularblackblob­­ontoanythingbelowourcharacter.Thisisquickerandeasierforthegraphicscardtodo,soitshouldworkwellonallrangesofhardware.

UnityincludesaBlob­ShadowprefabinitsStandardAssetscollection,soweshallusethisratherthancreatingourown.ThisassethasalreadybeenimportedandaddedtotheprojectintheBlob­Shadowfolder.OpenthisfolderandclickontheblobshadowprojectorPrefabanddragitontoourtop­levelcharacterobject­­Player­­intheHier­archyPane.ThisshouldaddtheProjectorjustbelowthetoplevelinourPlayerob­ject'shierarchy:

TheBlobShadowProjectorPrefabinthePlayer’shierarchy.

Next,youwillneedtomodifytheblobshadowprojector’sPositionandRotationdatasothatitisdirectlyaboveourcharacterandpointingdirectlydownattheground.

Selectthe4­Splitlayout.

30

Settheblobshadowprojector’sRotationvaluesto90,180and0respectively.

NowusethesideandtopviewstomovetheprojectordirectlyoverLerpz’head.Youmightwanttomoveitupordownalittleuntilyou’rehappywiththeshadow’ssize.

CreatinganewLayerAtthispointyouwillhavenoticedthattheblobisalsobeingprojectedontoLerpz.Wedon’twantthistohappen.Therearetwooptionstogetaroundthis:movetheNearClipPlanesettingfurtherawayfromtheprojector,orsimplytellitnottoprojectontoobjectsinspecificLayers.Weshallusethelatteroption.

WhynotadjusttheNearClipPlane?Thistechniquemightseemeasiestatfirstglance,buttheplanewouldneedtobead­justedbyscriptingtotakeintoaccountLerpz’sanimations.Hisfeetmovefurtheroutwhenhejumps,thenbrieflymovealittlecloserwhenhelandsagain.SincetheshadowmustalwaysbeprojectedontothegroundonwhichLerpzstands,thismeanstheNearClipPlanecannotremainthesamethroughoutthesesequences.

OpenupthePlayerGameObject.

OpentheLayerdrop­downintheInspector.

ChooseAddnewlayer…

ClickonthefirstemptyUserLayerentryandnameitnoShadow.

YoushouldnowseesomethinglikethisinyourInspector:

AddinganewLayerusingtheTagManager.

31

NowclickbackonthePlayerobjectintheHierarchyPanetobringuptheusualInspectorsettings.

Clickonthe“Layer”drop­downandsetittothenewlayername,noShadow.UnityasksifyouwishtoapplythistoallchildGameObjects:click“ChangeChil­drenLayers”.

NextweneedtotelltheBlobShadowProjectornottoprojectontoobjectsinthisLayer.

Bringuptheblobshadow’spropertiesintheInspectorandlookattheIgnoreLayersentryintheProjectorComponent.

Usethedrop­downmenutotherighttoselectthenoShadowLayer(whichshouldapplytochildrenaswell),asshown:

TheBlobShadowProjectorsettings.

Ifyounowplaythegameandmovearoundyoushouldseetheshadowbehavingprettymuchasexpected....exceptifyoujumparoundnearthecollectablefuelcells.Ifyoutrythis,youwillseetheitemshowingtheblobshadowtoo.

Wewantcollectableitemstostandoutatalltimes,itmakessensetotelltheBlobShadowProjectortoavoidthesetoo.

We’llbelookingatthesecollectablesinmuchmoredetailinthenextchapter,butlet’sfixthisproblemnowwhilewe’rehere.

First,stopthegame.

NowgototheProjectPaneandlocatetheFuelCellPrefabandHealthLifePick­UpPrefabobjects.You’llfindtheminsidethePropsfolder.

SelecttherootobjectofeachPrefabandsetitsLayertonoShadow,asshownbelow:

32

Layerchangedto“noShadow”

NOTE Whenmakingachangetoaparentobject,Unitywilloftenaskifthechangeshouldalsobeappliedtothatobject'schildren.Doingsocanbedangerousifyouhaven'tthoughtthroughalltheramifications.

Inthiscase,wewantallthechildobjectsofthe"FuelCellPrefab”and“Health­LifePickUpPrefab”GameObjectstobeinthesame"noShadow"layer,sowhenUnityasks,agreetopropagatethechanges.

ScriptingConceptsHistoryislitteredwithsurprisinglycomplexmachines­­knownasautomata­­builtbyourancestorsforthepurposesofentertainment.Somewereveryelaborateandcouldevenperformsimpleplaysusingpuppets.Otherswereinteractiveandchangedtheirbehavioraccordingtouserinput.Thesemachineswerefundamentallythesame:thedesignercreatedassets­­puppets,props,paintedbackdrops,etc.­­andthendesignedmachinerytomakethoseassetsbehaveastheydesired.

Thebasicprinciplehasremainedunchangedovertheyears.Computershavemerelyturnedphysicalmachinery,builtofsteelandsprings,intovirtualmachinerycontrolledbylistsofinstructions.Unityreferstosuchlistsofinstructionsasscripts.

Mostscriptsarecenteredonaconceptpopularingamedevelopment:theFiniteStateMachine.AFiniteStateMachineessentiallydefinesasystemofinteractingconditions,knownasstates.

Astatecanbealmostanything,suchaswhetheranobjectshouldberenderedatall,whetheritshouldbesubjecttothelawsofphysics,belitorcastashadow,whetheritcanbounce,itspositiononadisplay,andsoon.TheInspectorPaneletsuschangemanysuchstatesdirectlybecausethesestatesarecommontoalmostallgames.

However,thereisanothertypeofstatewhichisspecifictothegameitself.Unitydoesnotknowthattheplayer'savatarisanalien,howmuchdamageLerpzcantakeorthatLerpzhasajet­pack.HowcanUnitybeawareoftherobotguards'requiredbe­haviororhowtheyshouldinteractwithLerpz?

Thisiswherescriptscomein.Weusescriptstoaddtheinteractionandstatemanage­mentspecifictoourgame.

Ourgamewillneedtokeeptrackofanumberofstates.Theseinclude:

• Theplayer’shealth;

33

• Thenumberoffuelcanisterstheplayerhascollected;

• Whethertheplayerhascollectedenoughfueltounlocktheforcefield;

• Whethertheplayerhassteppedonajumppad;

• Whethertheplayerhastouchedacollectableitem;

• Whethertheplayerhastouchedthespaceship;

• Whethertheplayerhastouchedarespawnpoint;

• WhethertheGameOverorGameStartscreensshouldbeshown;

• ...andmore.

Manyofthesestatesrequireteststobemadeagainstotherobjects’statestoensurethey’reuptodate.Sometimesweevenneedintermediatestates,toaidatransition.Forexample,collectingafuelcanisterwillforceachecktobemadetoseeiftheplayerhasenoughtoshutdowntheforcefield.

Organization&StructureInthistutorialthestatemachinesfortheplayer,thelevelandtheenemiesarehan­dledbyabunchofscriptslinkedtovariousGameObjects.Thesescriptstalktoeachother,sendingeachothermessagesandcallingeachother'sfunctions.

Thereareanumberofwayswecansetuptheselinks:

• ByaddingalinkexposedintheInspector,ontowhichyoudroptherelevantobject.Thisisidealforgeneral­purposescriptswhichyouintendtore­useinotherprojects.

Thisisthemostefficientasthescriptmerelyplucksthedatafromtherelevantvari­ableanddoesn'tneedtodoanysearching.However,itdoesassumeyouknowinad­vanceexactlywhichobjectorcomponentyou'llbelinkingto.

Weusethisoptionforthecut­scenecamerasinLevelStatus.(Thisscript,currentlyjustashortstub,isalreadyattachedtotheLevelGameObject.)Thisgivesustheflexibilitytosetupmultiplecameras,oneforthe"levelexitunlocked"cut­sceneandanotherforthe"levelcomplete"sequence.Inpractice,we'reonlyusingtwocamerasinthegame;onefortheplayerandthe"levelcomplete"sequence,theotherforthe"un­locked"cut­scene.Buttheoptionistheretochangethis.

• Settingupalinkwithinthescript'sAwake()function.TheAwake()functioniscalled

oneveryscriptyouwritebeforethefirstUpdate()eventisfiredontheGameObjectitisattachedto.SettingupthelinkhereallowsyoutocachetheresultforlateruseinanUpdate()function.Typically,youwouldsetupaprivatevariablewithalinkto

anotherGameObjectorcomponentyouneedtoaccesswithinyourscript.Ifyouneed

34

todoaGameObject.Find()calltolocatetherelevantobject,itismuchbetterto

dosoonceonly,insideAwake()asGameObject.Find()isquiteslow.

Thisoptionismoresuitedtothosesituationswhereyoudon'tneedtheflexibilityofthefirstoption,butdon'twanttohavetoperformaconvolutedsearchfortheobjecteverygamecycle.Thesolutionisthereforetosearchfortheobjectwhenthescriptis'wokenup',storingtheresultsofthesearchforuseintheupdatesection.

Forexample,theLevelStatusscript,whichhandleslevelstate,cacheslinkstoanum­berofotherobjects,includingthePlayerobject.Weknowthesewon'tchange,sowemayaswellmakethecomputerdothisworkforus.

• SettingupalinkduringtheUpdate()function.Thisfunctioniscalledatleastonce

pergamecycle,soitisbesttoavoidusingslowfunctioncallshere.However,theGameObject.Find()andGetComponent()functionscanbequiteslow.

Thisoptionisusedforthosesituationswheretheobjectyouneedcouldchangeatanytimeduringthegameplay.

Forexample,whichofthemultipleRespawnpointsinthistutorial'sSceneshouldtheplayerberespawnedat?Thisclearlychangeswhilethegameisrunning,soweneedtohandlethisaccordingly.

Theproblemwiththisisthatit’sslow,soitisbesttodesignyourgamesuchthatyoudon’tneedtodothisoften.

ScriptsinaVisualDevelopmentEnvironmentUnityisanunusualtoolinthatitsfocusisonthevisualassetsratherthanthelinksandconnectionsbetweenthem.AlargeUnityprojectcanhavedozensofscriptsofvaryingcomplexitydottedaroundtheHierarchy,sothedesignusedforthistutorialusessomeobject­orientationtechniquestoalleviatethis.

Thescriptwhichdealswithaparticularpartofthestatemachine­­e.g.playeranima­tion­­shouldalsobetheonewhichkeepstrackoftherelevantstatevariables.Thiscanmakethingsalittlecomplicatedwhenascriptneedstoaccessastatevariablestoredinanotherscript,whichiswhysomescriptscachesomevalueslocallytomakeaccesstotheinformationquicker.Thistechniquealsooccasionallyresultsinchainsofcommands,whereafunctioninonescriptmerelycallsasimilarfunctioninanotherscript.Thehandlingoftheplayer'sdeathandhealthisanexampleofthis.

NOTE AsyougetmoreexperiencewithUnity,youwillfindotherwaystohandlestatesthatmaybebettersuitedtoyourowngames.Thedesignpatternusedinthistutorialshouldnotbeconsidereda“onesizefitsall”solution!

35

Ifyouwish,youcanturnourPlayerGameObjectintoaPrefabcontainingallourchanges,soyoucanreuseitinotherprojectsasastartingpoint:

ClickonthePlayerfolderintheProjectPane.

CreateanewPrefab.(It’llappearinsidethePlayerfolder.)

GivethenewPrefabanappropriatename­­IsuggestLerpzPrefab.

DragourPlayerGameObjectontoLerpzPrefabtocompetetheprocess.

Death&RebirthPlatformgamecharacterstendtoleadriskylivesandLerpzisnoexception.Weneedtoensurehelosesalifeifhefallsoffthelevel.Wealsoneedtomakehimre­appearatasafespotonthelevel­­oftencalleda“respawnpoint”­­sohecancontinuehisquest.

AnotherpointisthatifLerpzcanfalloffthelandscape,itisalsopossiblethatthelevel'sotherresidentscoulddosotoo,sothesemustalsobedealtwithappropriately.

Thebestsolutionforthisistouseaboxcollidertocatchanythingfallingoffthelevel.We'llmakeitverylongandbroad,sothatifaplayershouldtryusingthejet­packwhilejumpingoff,we'llstillcatchhim.However,Lerpzwillneedsomewheretores­pawn.We’llcometotherespawnpointsshortly.First,let’sbuildtheboxcollider:

CreateanemptyGameObject.

RenamethenewobjectFalloutCatcher.

AddaBoxColliderobjecttoit.

AddtheFalloutDeathscriptfromComponent­>ThirdPersonProps.

UsetheInspectortosetthevaluesasshowninthescreenshotbelow:

36

FalloutCatchersettings.

TheFalloutDeathscriptThisscriptisshortbecauseitsimplydelegatesalltheworktotheThirdPersonStatusscript.(ThisneedstobeattachedtoLerpz,butwewon’tdosojustyet.)

Thecodetohandlethecollider’striggerisinOnTriggerEnter().Thisfunctionis

calledbyUnitywhentheBoxColliderisstruckbyanotherGameObjectcontainingaCollidercomponent,suchasLerpzoranenemy.

Therearethreetests:onefortheplayer,oneforasimpleRigidBodyobject,andathirdtesttocheckiftheobjecthasaCharacterControllerComponent.Thesecondtestlooksforanypropssuchasboxesorcratesfallingoffthelevel.(Wedon’thavesuchitemsinthislevel,butyoucanaddoneifyouwouldliketoexperiment.)Thethirdtestisusedforenemiesasthesewon’thaveordinaryphysicsattached.

Iftheplayerhitstheboxcollider,thecodesimplycallstheFalloutDeath()func­tioninLerpz’sThirdPersonStatusscript.

IfanotherobjectwithacolliderobjecthitsourGameObject,wesimplydestroyit,re­movingitfromtheScene,otherwiseit’llfallforever.

Inaddition,wehave:

• Theutilityfunction­­ Reset()­­whichensuresanyrequiredComponentsarealso

present.ThisfunctioniscalledbyUnityautomaticallywhenaddingthecomponentfor

37

thefirsttime.ItcanalsobecalledintheEditorbyclickingonthecog­wheelicontotherightofanyComponent’snameintheInspector:

TheResetmenucommand.

• The@Scriptdirective,whichalsoaddsthescriptdirectlytoUnity’sComponentmenu.ThisisaconvenienceandsaveshavingtohuntforitinsidetheProjectPane­­usefulifyouhaveacomplexprojectwithlotsofassets.

Ifwetrytoplaythegameatthispoint,Unitywillcomplainbecauseitdoesn’tknowwheretomakeLerpzreappear.Thisiswhererespawnpointscomein...

RespawnPointsWhentheplayerdies,weneedsomewheresafeforhimtore­appear.Inthistutorial,Lerpzwillreappearatoneofthreerespawnpoints.WhenLerpztouchesoneofthesepoints,itwillbecomeactiveandthiswillbewherehereappearsifhedies.

LerpzstandingonanactiveRespawnpoint.

TherespawnpointsareinstancesoftheRespawnPrefabprefabobject.(You'llfinditintheProjectPane'sPropsfolder.)

Thisprefabisamodelofateleportbase,coupledwiththreecompleteparticlesys­tems,aspotlightandsomeotheroddsandends.Here'sthebasicstructure:

38

• RSBasecontainsthemodelitself:ashortcylindricalbasewithaglowingbluediscinthecenter.

• RSSpotlightisaspotlightobjectwhichshinesasubtlebluelightupfromthesurfaceofthemodel,givingtheillusionthatthebluetextureisglowing.

• Theremaininggameobjectsareparticlesystems.TheRespawnscriptattachedtotheparentRespawnPrefabobjectswitchesbetweentheseparticlesystemsdependingontheprefab'sstate:

‣ Iftherespawnpointisinactive,asmall,subtleparticleeffectisshownlookinglikeabrightbluemist.ThisiscontainedinRsParticlesInactive.

‣ Iftherespawnpointisactive,alarger,moreostentatiouseffectisshown.ThisiscontainedinRsParticlesActive.

Onlyonerespawnpointcanbeactiveonthelevelatanyonetime.Whentheplayertouchestherespawnpoint,acolliderobject(setasatrigger)detectsthisandtrig­gersactivationoftherespawnpoint.

‣ Theremainingthreeparticlesystems­­RSParticlesRepawn1,RSParticlesRepawn2andRSParticlesRepawn3­­areenabledtogetherwhentheplayerisrespawnedattherespawnpoint.Theseareone­shotparticlesystems.Thescriptletstheseplay,thenrestorestheRsParticlesActiveparticlesystemoncethisone­shotsequenceiscompleted.

Theprefabcontainsascript,Respawn,whichcontrolsthestateoftherespawnpoint.However,inorderforthegametoknowwhichspecificrespawnpointtheplayerneedstobereturnedtowhenhedies,weneedtoarrangetherespawnpointsinahierarchyunderamastercontrollerscript.Let’sdothisnow:

DragtheRespawnPrefabintotheSceneView.

Positionitasshownintheimageonthenextpage.

RenamethisinstanceRespawn1.

Repeattheabovestepstwicemore.Youcanplacethesewhereveryoulike­­addmoreifyouwish!Isuggestputtingonenearthearenaatthefarendofthelevel,andanothernearthetreesinthegardenabovetheplatforms.

ThenextstepistocreateacontainerGameObject.

RenamethisRespawnPoints

MakealltherespawnprefabinstanceschildrenofRespawnPoints.

39

Positioningthefirstrespawnpoint.(Lerpzhasbeenmovedoutoftheshotforclarity.)

HowitworksWhentheSceneisloaded,UnitycallstheStart()functionineachinstanceoftheRespawnscript,wheresomeusefulvariablesareinitializedandpointerstootherele­mentsarecached.

Thekeymechanismiscenteredaroundthisstaticvariable:

static var currentRespawn : Respawn;

ThisdefinesaglobalvariablenamedcurrentRespawn.

Thestatickeywordmeansitissharedacrossallinstancesofthescript.Thisletsus

keeptrackofwhichrespawnpointisthecurrent,activeone.However,whentheScenebegins,noneofthepointsisactivated,soweneedtosetadefaultoneforourScene.TheUnityInspectorwillnotdisplaystaticvariabletypesatall,sothescriptde­finesanInitialRespawnproperty,whichneedstobesetforeachinstance.

DragthedefaultRespawnpointontothis.

You’llneedtorepeatthisforallrespawnpointsinthescene.(Inthetutorialproject’scase,thedefaultissettoRespawn1,whichislocatedneartheJailanddirectlybelowtheplayer’sstartingpoint.)

NOTE Itisn’tpossibletosetthesepropertiesdirectlyintheoriginalPrefab.

40

Whenarespawnpointisactivatedbytheplayertriggeringitscollider,thatpoint'sRespawnscriptfirstdeactivatestheoldRespawnpointandthensetscurrentRespawntopointtoitself.TheSetActive()functiontakescareoffiringofftherelevantparticlesystemsandsoundeffects.

TherespawningoftheplayercharacterishandledbytheThirdPersonStatusscript,whichmanagesmostoftheplayer’sgamestate.

AddtheThirdPersonStatusscripttothePlayerGameObject.Thescriptcanbefoundin:Scripts­>Player­>ThirdPersonStatus.

Therespawnpointscriptsalsohandlesoundeffects.Theseareplayedasone­shotsamples,exceptforanAudioSourceattachedtoeachRespawnPrefab.ThisCompo­nentcontainsthe"active"sound,whichisaloop.Thescriptsimplyenablesordisablesthissoundasappropriate,eitherwhileplayingaone­shoteffect­­suchaswhentheplayerisactuallyrespawningoractivatingtherespawnpointitself­­orwhentheres­pawnhasbeendeactivated.

NOTE Unitymakesitalmosttooeasytoaddsoundeffects.Wheneveryouplantoaddsuchanasset,considercarefullyhowitwillbeused.Forexample,wehaven'tincludeda"respawndeactivated"soundbecauseyou'dneverhearthesoundbeingplayed;you’reunlikelytopositiontworespawnpointswithinear­shotofeachother.Ifyouweretoconverttheprojectintoamultiplayergame,youmightwanttoaddsuchasoundandthenecessaryscriptcodetohandleit.

Thescriptisnotcomplexandyoushouldfindthescriptcodeeasyenoughtofollow.Wewillreturntotherespawnpointsinlaterchapters.

41

SettingtheScene

FirstStepsInthissectionwewilllookatbuildingthegameworldwheretheactiontakesplace.Inmovieterminology,thismeansbuildingtheset,placingthepropsandwritingthescriptingthatletsourherointeractwiththem.

Ourfirststepistopreparethestage.Thetutorialfilealreadyhasthebasiclevelmeshsetupandpopulatedwithanumberofcollectableitems.Wewillplaceafewmorepropsandelements,butmosthavebeendoneforyouasplacingallofthesepropswouldmakeforaverylong,veryboringtutorial.

LightingThelevelisalreadyprovidedwithanambientlightaswellasmyriadpointlights.Theselightupthescenery,theplayer,theenemiesand,toalimitedextent,thepick­ups.

Inthisproject’scase,thelightswereplacedbytheartistwhomodeledthelevel.Light­ingplayssuchanimportantroleincreatingtheambienceandsenseofplacethatitisusuallybesttoleavethistothemodelerwhobuiltthelevel.

42

Withourheronowmobile,thenextstepistogivehimsomethingtodo...

PlacingPropsThebasicsetisprovidedforyou,alongwithanumberofpropsalreadyinplace.

BuildingYourOwnLevelsThetutoriallevelwasbuiltbyarrangingscenerycomponentsinMayaandthenim­portingthelevelintoUnity.Ifyouwouldliketoexperiment,orevencreateadditionallevels,theindividualsceneryelementscanbefoundintheBuildYourOwn!folderintheProjectPane.

Therearealargenumberofthefuelcellpropsandplacingallofthesewouldmakeforaverydulltutorial.Ifyouhavereadtheprevioustutorials,youwillalreadyknowhowtodothisanyway,sowewilllimittheplacementtothe"Health"pickups,thejump­padsandrespawnpoints,amongothers.

HealthPickupsWebeginwithasimpletask:addingsomecollectablehealthpickups.Thesearespin­ning,glowingheartsthataddhealthtoourplayer.ThepickupsarealreadydefinedasPrefabsintheProjectPane.Lookinsidethe"Props"folderandyouwillfindtheHealthLifePickUpPrefabobjectreadytouse.

Placingahealthpickup.

DragoneontotheSceneViewanduseUnity'spositioningtoolstopositionitsomewhereonthelevel.

Repeatthisprocessuntilyou'veplacedabouthalfadozenofthesearoundthemap.

43

Wherethey'relocatedisuptoyou,thoughtheyshouldn'tbetooeasytofindorgetto.Considerhowtheplayermightplaythelevelanddecidewherethebestplacesareforsuchpickups.Itisbestnottobetoogenerousorthegamewillbetooeasy.

Finally,weshouldgroupthesepickupsintoafolderofsomesorttoavoidhavingthemclutteruptheHierarchyPane.WecandothisbycreatinganemptyGameObjectusingtheGameObject­>CreateEmptymenuitem.RenamethisnewobjecttoHealthPickups anduseittogroupyourhealthpickups,asshown:

Healthpickupshierarchy.

TheForceField

Theforcefield.

Atthemoment,theforcefieldtrappingourhero’sspaceshipdoesn’tanimate:it’sjustastaticmeshtexture.Theresultisvisuallydisappointing.

44

Thereareanumberofwaystoachieveadecentvisualeffect,butwhichtochoose?Sometimesasimplesolutionisthebest:wewilljustanimatethetexture’sUVcoordi­natestogiveittheeffectofaripplingforcefield.

TheanimationwillbedoneusingashortscriptwhichcanbefoundinsidetheScripts­>MiscfolderintheProjectPane.Itisnamed,FenceTextureOffsetandlookslikethis:

var scrollSpeed = 0.25;

function FixedUpdate() { var offset = Time.time * scrollSpeed; renderer.material.mainTextureOffset = Vector2 (offset,offset);}

ThefirstlineexposesapropertywecaneditdirectlyintheUnityinterface,ScrollSpeed.TheFixedUpdate()functioniscalledasetnumberoftimespersecondby

Unity.Weuseashortformula­­multiplytheScrollSpeedvaluebythecurrenttime­­todefineatextureoffset.

PrettyPropertiesWhenUnity’sInspectordisplayspropertiesandvariables,theirnamesareadjustedtomakethemlooknicer.Usually,thisjustmeanslookingforthecapitallettersinthenameandinsertingaspacebeforeeachone.Inaddition,Unitycapitalizesthefirstlet­terofthename.ThusscrollSpeedisdisplayedasScrollSpeed.

Whenatextureisrendered,thetextureitselfisusuallyjustanimage.ThemainTex­tureOffsetpropertyofamaterialtellsUnitythatitistodrawthetexture’simageoff­setwithinitsUVspace.Thiscanbeusedtoproducesomeveryeffectiveresultswith­outresortingtocomplexanimationsequences.

ExpandthelevelGeometryGameObjecttorevealthedifferentelementsoftheleveldata.WeneedtoanimatethefenceontheimpoundFenceobject,sodropthefence­TextureOffsetscriptontothis.

ScriptingtheCollectableItemsAtthemomentLerpzdoesnotpickupanyoftheitemsonthelevel.ThisisbecauseUnityhasnotbeentoldtoletourherodothis.Weneedtoaddtwoelementstoeachcollectableitem:

45

• AColliderComponent,

• ScriptcodetohandletheColliderandupdateplayerhealth,etc.

ThecollectibleitemsintheHierarchyPaneareallPrefabinstances,whicharedis­playedinblue.ByeditingtheoriginalPrefabsdirectly,wewillautomaticallyupdatealltheitemsinthegame.

ThetwoprefabsourherocancollectareFuelCellPrefabandHealthPickUpPrefab.ThesecanbefoundinsidethePropsfolderintheProjectPane.

SelecttherootHealthPickUpPrefabobject.

UseComponent­>Physics­>AddSphereCollidertoaddaspherecollidertothePrefab.YoushouldseeitappearintheInspector.

Finally,SettheIsTriggercheckbox.

TheHealthPickUpPrefabintheInspector.

NOTE AnObjectRotaterscriptisalreadyattachedtothePrefab.Thisjustmakesthepickupspinonthespotandisverysimple.We’llseeamorecomplexexampleofscriptedanimationinalaterchapter.

46

Collidershavetwouses:wecanhitthemwithsomethingelse,orwecanusethemasTriggers.

TriggersTriggersareinvisibleComponentswhich,astheirnameimplies,triggeranevent.InUnity,aTriggerissimplyaColliderwithitsIsTriggerpropertyset.ThismeanswhensomethingcollideswiththeTrigger,itwillactlikeavirtualswitchinsteadofaphysicalentity.

Triggerswillsendoneofthreeeventmessageswhensomethingsetsthemoff:On-

TriggerEnter(),OnTriggerStay()andOnTriggerExit().

Triggereventmessagesaresenttoanyscriptattachedtothetriggerobject,sonowweneedtoaddasuitablescripttoourhealthprefab:

GototheComponentsmenuandchoosethePickupscriptfromtheThirdPersonPropssub­menu.ThiswilladdthePickupscripttoourPrefab.

SetthePickupTypepropertyintheInspectortoHealthasshownintheimageabove.

Finally,settheAmountpropertyto3orso.Thisistheamountofhealththepickupbestowsontheplayer.

Howmuchhealth?Theheads­updisplay,or‘HUD’,whichshowstheplayer’scurrenthealthlevel,lives,etc.,canonlyhandleamaximumhealthlevelofsix.Whathappensiftheplayercol­lectsahealthpickupwhenhealreadyhasafullhealthbar?Thisisamatteroftaste,butI’vechosentomakethistriggertheadditionofanextralife.Thelogicforthiscanbefoundintheplayer’sstatecheckingscript,ThirdPersonStatus.

Thefuelcellpickupsaresetinmuchthesameway,withtheonlytwodifferences:

• ThePickupTypesettingshouldbeFuelCell,

• TheAmountvalue,whichistheamountoffuelthepickuprepresents.(1seemsbest.)

47

JumpPads

AJumpPad.

TheJumpPadsarethebrightyellowandblackstripedspacesinourlevel.ThesearesupposedtoboostLerpzintotheair.Weshalluseacolliderwithanattachedscriptforthispurpose.

First,createanemptyGameObjectandcallitJumpPadTriggers.We’llusethislikeafoldertokeepourJumpPadtriggerobjectstogether.

Nowwe’llbuildourPrefab:

CreateanewemptyGameObject.

RenamethisobjecttoJumpPadTrigger1.

AddaBoxColliderobjecttoit.(ASphereColliderwouldworkintheory,buttheBoxColliderisabetterfitgiventhejumppad’sshape.)

SettheColliderasaTrigger.

AddtheJumpPadscript.

That’stheobjectcreated.NowweneedtoturnitintoaPrefab:

ChoosePrefabfromtheCreatemenuabovetheProjectpane.

48

DraganddropourJumpPadgameobjectontothenewPrefab.

RenamethePrefabtoJumpPadTrigger.

DeletetheoriginalGameObjectfromourHierarchypane.

DragaJumpPadPrefabintothesceneandpositionitdirectlyinsideoneoftheJumpPadlocations.(Therearesixtoplace.Irecommendusingthe4Splitviewlayouttohelpwithpositioning.)

ThedefaultjumppadJumpHeightsettingof5isn’tenoughtothrowLerpzrightuptothegardenlevel.Isuggestusingavalueofaround15­30forthesepads.(Thefinishedprojectexampleusesavalueof30.)

Finally,hitPlayandtestthegametomakesureallournewtriggersworkcor­rectly.

NOTE ScriptsworksimilarlytoPrefabs:we’vejustaddedalinktothePickupscriptinthePropsfolder­­toourPrefab.EditingtheoneinourProjectpanewillalsoaffectanycopiesintheScene.

Goodorganizationisimportantifyouwantyourworkflowtobesmoothandhasslefree.

• UseinstantiatedPrefabswhereverpossible.

• Tryorganizingbyfunctioninsteadoftype.

• UseEmptyGameObjectsascontainers.

Youwillbesurprisedathowmanyassetsareneededforevenasmall­scaleproject.

49

TheGUI

TheUserInterfaceGamesusuallyhaveGraphicalUserInterfaces(GUIs),suchasmenus,optionsscreensandsoon.Furthermore,gamesoftenhaveaGUIoverlaidontopofthegameitself.Thiscouldbeassimpleasascoredisplayedinacorner,oramoreelaboratedesigninvolvingicons,inventorydisplaysandhealthstatusbars.

Unity2introducesanewGUIsystemtomakeiteasytobuildsuchGUIsforgamesandthisisthesystemweshalluseforLerpzEscapes.

NOTE TheoldsystemwillremainfortheUnity2.xreleases,butwillbedeprecatedinafutureversionofUnity.Itisnotcoveredinthistutorial.

Unity2'snewGUIsystemPreviously,youwouldtellUnitytodrawabuttonandUnitywouldfireoffrelevantmessagestoyourscriptwhentheuserhoveredoverthebutton,clickedthebuttononit,releasedthebutton,andsoon.

TheoldsystemwasbasedonthetraditionalEvent­DrivenGUImodel,butUnity2in­troducesabrandnewGUIsystem,knownasanImmediateModeGUI.IfyouareusedtotraditionalGUIsystems,theImmediateModeGUIconceptmaycomeasashock.

Here'sanexample:

Howmanylivesdowehavere­maining?WhatisLerpz’shealthlevel?It’stimeforaGUI.

function OnGUI(){ If (GUI.Button (Rect(50, 50, 100, 20), "Start Game") ) Application.LoadLevel("FirstLevel"); // load the level.}

OnGUI()iscalledatleasttwiceeverygamecycle.Inthefirstcall,UnitybuildstheGUI

anddrawsit.Inthiscase,wegetasimplebuttondrawnatthecoordinatesspecified,with"StartGame"displayedwithin.

Thesecondcalliswhenuserinputisprocessed.Iftheuserclicksonthebutton,theIf(...)conditionalsurroundingthebutton­drawingfunctionreturnstrue,so

Application.LoadLevel()willbecalled.

OtherGUIelements­­Labels,Groups,Check­boxes,etc.­­allworksimilarly,withthefunctionsreturningtrue/false,oruserinputasappropriate.

Theobviousadvantagehereisthatyoudon'tneedumpteeneventhandlersforaGUI.It'sallcontainedintheoneOnGUI()function.

Unity2providestwosetsofImmediateModeGUIfunctions:thebasicGUIclassasusedintheexampleabove,andasimilarGUILayoutclass,whichhandlesthelayoutofGUIelementsforyoutosavetime.

FurtherInformationMoreinformationonthenewUnityGUIsystemcanbefoundbyfollowingtheselinks:

• http://unity3d.com/support/documentation/Components/GUI%20Scripting%20Guide.html

• http://unity3d.com/support/documentation/ScriptReference/GUI.html

TheIn­gameHUDOurgameneedsanin­gameGUItodisplaytheplayer’shealth,livesremainingandthenumberoffuelcellsheneedstocollect.Thegraphicalelementsarealreadyincludedinourprojectfile.

TheGUIishandledwithintheGameHUDscript,whichusesthenewGUIcomponenttolayoutthevariouselements.ThisscriptneedstobeattachedtotheLevelGameOb­ject,whichisusedtoholdScene­specificelements.(WecouldjustaseasilyhaveaddedittotheNearCameraobjectortoitsown'GUI'GameObject;thisismainlyamatterofpersonaltasteratherthanakeygamedesigndecision.)

WewillusetheLevelGameObjecttomanagelevel­specificstatesandotherscripts.

51

TheGUISkinobjectUnity2’snewGUIsystemincludessupportforskinning.ThisgivesyoufullcontroloverthelookandfeelofeveryGUIelement.BuildingyourownGUIskincontentletsyouchangetheshapeofabutton,itsimagery,itsfont,itscolorsanddothesametoeveryotherGUIelement,fromtextinputboxesthroughtoscrollbarsandevenwholewin­dows.

Asourin­gameGUIwillbebasedentirelyaroundgraphicalimages,wewillbuildthegame’sHUDentirelyusingtheGUI.Label()function.However,wedoneedtouse

acustomfontforourHUD,inordertodisplaytheremainingfuelcansandlives.

TheGUISkinassetdefinesthe‘look’ofaUnityGUI,muchasaCSSfiledefinesthelookofawebsite.Theobjectisrequiredifyouneedtochangeanydefaultfeatures.Aswearechangingthefont,weneedtoincludeaGUISkininourScene.

UsetheAssetsmenucommandtocreateanewGUISkinobject.ThiswillappearintheProjectPaneandcontainsthedefaultUnityGUIskindata.

RenamethenewGUISkinobjecttoLerpzTutorialSkin.

Wearegoingtouseadecorativefont,named“Flouride”,forourgame.Thisistheonlychangewearemakingtothedefaultskin.

DragtheFlouridefontobjectontoournewGUISkinasset’s“Font”entry:

GUISkin,settingthefont.

TheGUISkinobjectisnotaddedtotheHierarchyPaneview;insteadwereferencetheGUISkindirectlyinourGameHUDscript.AlongwiththeGUISkin,theGameHUDscriptalsoneedstobetoldwhichassetstousetobuildtheGUIdisplay.TheseincludetheGUIHealthRingasset.

52

TheGUIHealthRingimage

NOTE YoumayhavenoticedanumberofwarningsfromUnityaboutimagesnotbeinga“poweroftwo”insize.Manygraphicscardspreferimagestobesizedinpowersoftwo,regardlessofhowmuchoftheactualimagedataisused,asthismakestheunderlyingcalculationsquickertoperform.GUIsrarelyneedthislevelofoptimizationandtheassetsweareusingarethereforenotopti­mized.Ifyouwanttofindoutwhetheranimage’sdimensionsarepowersoftwo,simplybringtheimageupintheInspectorwhichwillletyouknowifitneedssuchoptimizing.

ThisimageisusedtodisplayLerpz'shealthinformation.ThespacetotherightofLerpz'simagedisplayshisremaininglives,whilethecircletotheleftisusedtoshowapiechartofhisremaininghealth.Thepiechartiscreatedbysimplysuper­imposingthecorrectimagefromanarrayofsix2Dtextures,namedhealthPie1throughhealth­Pie6.ThehealthPie5imageisshownbelow:

ThehealthPie5image

NOTE Theseimagesincludealphachannelstodefinetransparencyandtranslucency.

UsingseparateimagesletsussimplydrawtheimagecorrespondingtoLerpz'sactualhealth,insteadofperformingfancycalculationstorotateanddrawsegmentspro­grammatically.

53

ThesecondmajorGUIelementisfortheFuelCellstate,themainimageforwhichisGUIFuelCell:

TheGUIFuelCellimage

Thisisdisplayedinthelower­rightofthegamescreenandwillshowthefuelcellsremainingtobecollectedbeforethelevelisunlocked.

AddtheGameHUDscripttotheLevelGameObject.

ClicktheLevelGameObjecttoselectitandlookattheInspector.YoushouldseetheGameHUD(Script)componententry.

AddtheGUIHealthRingandGUIFuelCellimagestotheGameHUDscript.

OpenuptheHealthPieImagesentry.

HealthPieImagesisanarray.Atthemoment,Unitydoesn'tknowhowbigitshouldbe,soithassetittozero.Wehavesixhealthpieimagestodropintothisarray,soweneedtochangethisvalue.

Clickonthe"0"nexttoSize.Changeitto"6".Youshouldnowseesixemptyelements,namedElement0throughElement5.

OpentheGUIassetsfolderintheProjectPanetorevealthehealthpieimages.Therearesixofthese,numbered1to6.

Computerscountfromzero,sohealthPie1needstogointoElement0.health­Pie2needstogointoElement1...andsoon.Dragtheimagesintotheirrelevantslots.

FinallyaddtheLerpzTutorialSkinGUISkinintotheemptyGuiSkinslot.Theset­tingsshownowlooklikethis:

54

GameHUDscriptsettings.

Ifyourunthegamenow,youshouldseetheHUDappearingovertheplayarea:

Thein­gameHUD

ResolutionIndependence.

OneproblemwiththeGUIisitssize.Thescreenshotaboveisfroma24"iMacrunningataresolutionof1920x1200.

ClearlyweneedtoscaleourHUDdynamicallyaccordingtothecurrentdisplaysizeandresolution,sohowdoweachievethis?

Unity2'snewGUIsystemincludessupportforatransformmatrix.Thismatrixisap­pliedtoallGUIelementspriortorendering,sotheycantransformed,rotatedorscaled­­inanycombination­­dynamically.

55

GUI.matrix = Matrix4x4.TRS (Vector3.zero, Quaternion.identity, Vector3 (Screen.width / 1920.0, Screen.height / 1200.0, 1));

Thelinebelow,fromtheGameHUDscript,showshow:

IfyougototheGameView,disablethe"MazimizeonPlay"optionandsettheaspectrationto4:3,youwillseethattheGUIre­scalestofit.

Ifwewished,wecouldhaveourHUDspinaround,flipupsidedownorzoominfromadistance.Forgamemenus,highscorescreensandthelike,thisisausefulfeaturetohaveandwe'llusethistrickforourlevel­completesequence.

TheStartMenuEverygameneedsastartmenu.Thisisdisplayedwhenthegamestartsandletstheplayerchangeoptions,loadasavedgameand,mostimportantly,startplayingthegame.Inthissection,wewillbuildastartmenufromscratch.

NOTE Splashscreens,menusandthelikearealljustUnityScenes.ThusagamelevelisusuallyaScene,butaSceneisnotalwaysagamelevel.WeusescriptsinoneScenetoloadandrunotherScenestolinkScenestogether.

FortheStartMenu,wewillneed:

• TwoGUItextbuttons:"Play"and"Quit".

• Thenameofthegame.Thiswillberenderedusingacustomfont.

• Somesuitablemusic.

• Abackdropofsomesort.

Inotherwords,somethinglikethis:

56

TheStartMenu.

SettingtheSceneThefirststepistocreateanew,emptyScene.

TypeCMD+NonMacorCtrl+NonPC,tocreateone,thenCMD+SorCtrl+Stosaveit.

NameitStartMenu.UnitywillautomaticallyaddaCameratotheSceneforus,butthereisnothingforittoseeatthemoment.

Nowwe'llusethenewGUIsystemtobuildamenu:

GototheProjectPaneandcreateablankJavaScriptfile.

RenameitStartMenuGUIandopenitintheeditor.

First,we’lladdaUnityscriptdirective.DirectivesarecommandswhichgiveUnityin­formationoradditionalinstructionsaboutthescript.Thesecommandsaren’tpartofJavascriptassuch,butaimedatUnityitself.Youcanfindthecompleteassembledscriptcodelistedintheappendixsection.

Inthiscase,wewantUnitytorunourscriptinsidetheEditor,sothatwecanseetheresultsimmediatelywithouthavingtostopandre­runtheprojecteachtime:

// Make the script also execute in edit mode@script ExecuteInEditMode()

57

WeneedalinktotheLerpzTutorialSkinasset,sothefirstlineofcodewillbethis:

var gSkin : GUISkin;

We’llneedaTexture2Dobjectforthebackdrop.(We’lldropourbackgroundimage

ontothisintheInspector.)

var backdrop : Texture2D; // our backdrop image goes in here.

Wealsowanttodisplaya"Loading..."messagewhentheplayerclicksonthe"Play"button,sowe'llneedaflagtohandlethis:

private var isLoading = false; // if true, we'll display the "Loading..." message.

Finally,wegettotheOnGUIfunctionitself:

function OnGUI(){ if (gSkin) GUI.skin = gSkin; else Debug.Log("StartMenuGUI: GUI Skin object missing!");

ThecodeabovechecksifwehavealinktoavalidGUISkinobject.TheDebug.Log()

functionspitsoutanerrormessageifnot.(It'sagoodhabittosanity­checkanyexter­nallinksordatainthiswayasitmakesdebuggingmucheasier.)

TheBackdrop.ThebackdropimageisaGUI.Labelelementsettouseourbackgroundimageastheelement’sbackground.Ithasnotextandisalwayssettothesizeofourdisplay,soitfillsthescreen.

var backgroundStyle : GUIStyle = new GUIStyle(); backgroundStyle.normal.background = backdrop; GUI.Label ( Rect( (Screen.width - (Screen.height * 2)) * 0.75, 0, Screen.height * 2, Screen.height), "", backgroundStyle);

58

First,wedefineanewGUIStyleobject,whichwe’llusetooverridethedefaultGUISkinstyle.Inthisinstance,we’rejustchangingthe“normal.background”styleele­menttouseourbackdropimage.

TheGUI.Label()functiontakesaRectobject.Thisrectangle’sdimensionsarede­

rivedfromthedisplay’sdimensions,sothattheimagealwaysfillsthescreen.Theim­age’saspectratioisalsotakenintoaccount,sothattheimageiscroppedand/orres­caledtofitwithoutaddingdistortion.

Nextwecometothetitletext.Beforewewritethescriptcodeforthis,weneedtotakeanotherlookatourGUISkinandmakeasmallmodificationtoit:

OurmenuusesthedefaultfontdefinedintheLerpzTutorialSkinasset.Atthemo­ment,thedefaultstylefordisplayingtextisunsuitableforalargegametitle,sowewilladdanewcustomGUIStyletotheskin,namedmainMenuTitle:

GototheProjectPaneandclickonLerpzTutorialSkintobringupitsdetailsintheInspector.

WewillnowaddaCustomStyletoourGUISkin:

DefiningacustomstyleinourGUISkinobject.

Openup“CustomStyles”andensure“Size”issetto“1”.Youshouldseean“Element0”entry.

59

Open“Element0”andsetitselementsasshownabove.Specifically:settheTextColortotheorange­brownshown.(Don’tworryabouttheelementsnotshowninthescreenshot:theycanbeleftastheyare.)

Finally,wecannowaddtheremainingcodetoourscript:

GUI.Label ( Rect( (Screen.width/2)-197, 50, 400, 100), "Lerpz Escapes", "mainMenuTitle");

NOTE GUIStylenamesarecase­insensitivewhenyouattempttoaccessthemthroughscripting.Typing“mainMenuStyle”isthesameas“mainmenustyle”.

TheButtons.WenowneedtomodifytheGUIButtonpropertiesoftheLerpzTutorialSkinobjecttoproduceamoreinterestingbutton.

We'reusingthesameGUISkinobjectforthismenuandforthein­gameHeads­UpDisplay,or'HUD'.FortheHUD,onlythedefaultfontneedstobechanged.However,fortheStartMenubuttons,wealsoneedtohaveagraphicalimagebehindthebut­tontext.Thedefaultbuttondesigndoesn'tfitthegame'svisualstyle,soweneedtochangeit.

TIP IfyouwantyourGUIelementstoreacttoHover,FocusandActiveevents,youmustsetabackgroundimagetoo,eveniftheimageisblank.

ClickontheLerpzTutorialSkinassetintheProjectPanetobringupitsdetailsintheInspectorPane.Changeittothesettingsshownbelow­­ignoretheotherGUIelementtypes;wewon'tbeusingthem:

60

SettingthebuttonimagesintheLerpzTutorialSkinGUISkinobject.

Nowlet'saddthe“Play”button:

if (GUI.Button( Rect( (Screen.width/2)-70, Screen.height - 160, 140, 70), "Play")) { isLoading = true; Application.LoadLevel("TheGame"); // load the game level. }

Theaboveisallittakestorenderandhandlethe"Play"button.Thecodeforboththerenderingandtheeventhandlingisinthesameplace,makingiteasiertomaintain.TheGUI.Button()functiontakesaRectobjecttodefinethebutton'spositionand

size,followedbythetextlabel.

Iftheuserclicksonthisbuttonthebuttonfunctionreturnstrue,sowecanloadthegamelevel.

WesetisLoadingsothatweknowtoshowthe"Loading..."text,thentellUnitytoloadthegamelevel.

61

TIP ThenewGUIsystemsupportsanumberofalternativefunctionsfordrawingelements,allowingyoutospecifyanimageinsteadoftext,orevenanimage,alabelandatooltipcombined.Seethedocumentationformoredetails.

The"Quit"buttonishandledsimilarly,butwithatesttoensurethisisrunningasastandalonebuild(orintheUnityeditor)added.

var isWebPlayer = (Application.platform == RuntimePlatform.OSXWebPlayer || Application.platform == RuntimePlatform.WindowsWebPlayer); if (!isWebPlayer) { if (GUI.Button( Rect( (Screen.width/2)-70, Screen.height - 80, 140, 70), "Quit")) Application.Quit(); }

Asyoucansee,thisisverysimilartothe"Play"button.Wejustdrawitalittlelowerdownandcheckifweneedtodrawitatallfirst.

Thefinalstepisthe"Loading..."text,whichneedstobedisplayedwhentheuserse­lectsthe"Play"button.ThisisbecauseitcantakeafewmomentsforourgameScenetoload­­especiallyifit'sbeingstreamedovertheInternetinsidethewebplayer.

ThisiswhereisLoadingcomesin:

if (isLoading) GUI.Label ( Rect( (Screen.width/2)-110, (Screen.height / 2) - 60, 400, 70), "Loading...", "mainMenuTitle"); }

Again,wemakeuseofthemainMenuTitleCustomGUIstylesothatthetextstylematchesthatofthetitle.(GUIStylesarecase­insensitive,sotheupper­caselettersarepurelyforreadability.)

TheQuitButton.The"Quit"buttonwillonlyworkifyou'rerunningthegameasastandaloneapp.Ifit'sbeingplayedinaweb­player,DashboardwidgetorinsidetheUnityEditoritself,the"Quit"buttonwillnotdoanything.TheUnityweb­playercannotquitasit'sem­beddedinawebpage.Norisitagoodideaforittoclosethebrowser.(Youcouldar­guethatclosingthepagetheweb­playerisrunningonmightbeanoption,butthisisbesthandledbythewebpageitself.)Similarly,whatcoulda“Quit”optiondoinaDashboardwidget?ClosingthewidgetwoulddeleteitfromtheDashboard.

62

So,weneedtodisabletheQuitbuttonandstopitdisplayingifthegameisn'trunningasastandalone.TheoneexceptionisifthegameisbeingruninsidetheUnityEditoritself.Thisisbecausewewanttoknowifthebuttonisbeingdisplayedintherightplaceandbehavingitself,whichisdifficultifyoucan'tseeit.

Thenextstepistoaddsomemusic.

GototheProjectPane,openuptheSoundsfolderanddragtheStartMenuaudiofileontotheMainCameraobjectintheHierarchyPane.ThiswilladdanAudioClipcomponent.

ClickontheMainCameraobjectnowand,intheInspector,locatethenewAudioClipcomponent.EnablethePlayOnAwakeandLoopcheckboxes.

AddtheStartMenuGUIscripttotheMainCamera.

Finally,insidetheStartMenuGUIcomponent,setGSkintoLerpzTutorialSkinandBackdroptoStartSplashScreen.

Ifyounowplaythescene,youshouldseesomethinglikethisintheGameView,ac­companiedbyashort,loopingorchestraltune:

TheStartMenuinaction.

Sothat’sit!OurStartMenuisdone.

TIP ThemusicwascreatedusingAppleLoopsfromApple’sOrchestralJamPack,arrangedinGarageband.ThisisahandytoolforMacuserstobuildplace­holdertunes;youcanusethesetodecidewhichmusicalstylebestfitsyourgame.ForPCuserswesuggestusingAudacity.

Foryourownprojects,youwillmostlikelywanttoaddanOptionsMenu,perhapsahighscoremenu,amultiplayerlobby,etc.ThesecanallbebuiltusingtheUnity2GUI.

63

GameOverTheGameOverscreenisshownwhentheplayerhaseithercompletedthegameorhasfailedthechallenge.UnliketheStartMenu,thisScenehasnobuttonsorothervisualuserinteraction:itjustshowsa“GameOver”messageoverabackdropwhileplayingashortjingle.Oncethejingleiscompleted,oriftheuserclicksthemouse,weautomaticallyloadthe“StartMenu”Scene.

Firstly,createanewSceneandnameit“GameOver”

DraganddroptheGameOverJingleaudiofileontothedefaultMainCameraobjectandsetitasshown:

TheGameOverJinglesettings.

Wedon’tneedtoaddanythingelsetotheSceneusingtheEditor:thedefaultcameraalonewillsuffice.

Thenextstepistobuildourscript(Youcanfindthecompleteassembledscriptcodelistedintheappendixsection):

CreateanewJavascriptscriptassetandnameit“GameOverGUI”

Openitintheeditorandaddthecodedescribedbelow:

AswiththeStartMenu,wewanttobeabletoseeourGUIintheUnityeditorevenwhentheprojectisn’trunning,soaddthis:

@script ExecuteInEditMode()

FortheStartMenu,weusedtheLerpzTutorialSkinGUISkinasset.TheGUISkindefines abunchofGUIStylesandletsusapplythemtoaGUIwholesale.

64

AnalternativetechniqueistodefineindividualGUIStyleobjectsdirectly.WewilldosofortheGameOverscriptbydefiningthreeGUIStylevariableswhichwecanthensetintheInspector,alongwithtwovariablesdefiningthescalingofthetextelements:

var background : GUIStyle;var gameOverText : GUIStyle;var gameOverShadow : GUIStyle;

We’llsettheseGUIStyleobjectsintheInspectorshortly.

Next,weneedtodefinethetextscalingfactorforour“GameOver”messageasitwillberenderedlargerthanthedefaultfontsize.

We’regoingtodrawthismessagetwice,intwodifferentcolors,togiveashadowedtexteffect,sowe’lldefinetwoscalevariables:

var gameOverScale = 1.5;var gameOverShadowScale = 1.5;

AtlastwegettotheOnGUI()function:

function OnGUI(){

First,thebackdrop,rescaledsimilarlytothatusedintheStartMenu:

GUI.Label ( Rect( (Screen.width - (Screen.height * 2)) * 0.75, 0, Screen.height * 2, Screen.height), "", background);

Thenexttaskistodrawtheshadowedversionofthe“GameOver”message.Weneedtoscalethetextupandrenderitcenteredonthescreen.Luckily,wecanusetheGUIsystem’sbuilt­intransformmatrixtohandlethescalingforus.

TIP TheGUItransformmatrixcanalsobeusedtoperformanyarbitrarytransla­tionsyouwish:youcanscale,rotate,flipandspintoyourheart’scontent.

Toensurethetextappearsinadark,shadowcolor,wepassthegameOverShadowGUIStyletotheGUI.Labelfunction.

65

GUI.matrix = Matrix4x4.TRS(Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameOverShadowScale); GUI.Label ( Rect( (Screen.width / (2 * gameOverShadowScale)) - 150, (Screen.height / (2 * gameOverShadowScale)) - 40, 300, 100), "Game Over", gameOverShadow);

Finally,wedrawthesametextagain,butinalightercolor.Unity’sGUIsystemwillal­waysrendertheseelementsintheordertheyappearinthecode,sothistextwillap­pearontopoftheshadow.AsidefromusingthegameOverScalescalingfactorandthegameOverTextGUIStyle,there’snootherdifference.

GUI.matrix = Matrix4x4.TRS(Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameOverScale); GUI.Label ( Rect( (Screen.width / (2 * gameOverScale)) - 150, (Screen.height / (2 * gameOverScale)) - 40, 300, 100), "Game Over", gameOverText);}

SavethescriptanddropontotheMainCamera.

ClicktheMainCameraobjecttobringitupintheInspector.It’stimetosetthevariables...

First,theBackgroundGUIStyle.Thisjustneedsthe“GameOverSplashScreen”imagedroppedintotheNormal­>Backgroundslot,asshownbelow:

ThepertinentBackgroundGUIStylesettings.

Next,we’llsettheGameOverTextGUIStyleasshowninthenextimage.(Asusual,leaveanyothersettingsastheyare.)

66

ThepertinentGameOverTextGUIStylesettings.

Next,settheGameOverScaleto1.69.

NowfortheGameOverShadowGUIsettings:

ThepertinentGameOverShadowGUIStylesettings.

Finally,settheGameOverShadowScaleto1.61.

67

YoushouldnowseetheGUIappearintheGameView,asshownbelow:

TheGameOverscreen.

ThefinaltouchistoaddasecondscripttotheMainCamerawhichchecksifthemusichasfinishedplayingand,ifso,loadsuptheStartMenuScene.

CreateanewJavascriptScriptasset.NameitGameOverScript.

Openthescriptinyoureditorandaddthefollowingcode(Youcanfindthecompleteassembledscriptcodelistedintheappendixsection):

function LateUpdate () { if (!audio.isPlaying || Input.anyKeyDown) Application.LoadLevel("StartMenu");}

Thiscodechecksiftheaudiohasfinishedplaying,orwhethertheplayerhaspressedakeybeforeloadingthe“StartMenu”Scene.

AddGameOverScripttotheMainCameraandthat’sit:GUIsdone!

68

Adversaries

Antagonists&ConflictThesetwoelementsarekeytoanygame,soweneedsomethingtokeepLerpzonhistoes.Thejobofthegamedesigneristothrowobstaclesintothepathoftheplayer,butmakethemsurmountable.

Lerpzfacestwoadversaries:robotguardsandlaserbarriers.

TheLaserTrapsTheLaserTrapsarelocatedinthelaserpassagesandwillharmourplayerifheshouldtouchthebeam.Theimagebelowshowstwoofthese.Wewilllocatethemintheshortpassagewaystructuresoneithersideofthearena,atthefarendofthelevelfromthejail.

Nogameiscompletewithoutad­versaries.Inthischapter,weaddenemiesforLerpztofight.

LerpzfacestwoLaserTraps.

Thelasersriseandfall.Iftheplayertriestopassthroughoneofthese,hewilllosesomehealth.

ImplementingtheLaserTrapsEachlasertrapisjustabeamwhichrisesandfallsinasameverticalplane.IfLerpz(oranenemy)shouldhappentohitthebeam,it’llcausedamage.

ThelaserbeamitselfisproducedbyaLineRenderercomponentcontainedinitsownGameObject.ItsmovementandthelogicwhichcontrolsitisentirelycontainedintheLaserTrapscript.Solet’sbuildourfirstLaserTrap:

CreateanemptyGameObject.

Renameitto“Laser”

AddaLineRendererComponent(Component­>Miscellaneous­>LineRenderer).

NOTE Don’ttrypositioningthelaseryet!Youwillneedtodisablethe“UseWorldSpace”checkboxfirst;we’llcomethatshortly.

AddtheLaserTrapscript.

70

AdjusttheLineRendererComponentsettingsasshown.(Thelasermaterialcanbefoundin:Particles­>Sources­>Materials.)

LineRenderersettings.

Positiontheresultingobjectinthelasertunnels.(Thesearetheroofedcorridorstructuresoneithersideofthearena,atthefarendofthelevelfromtheJail.)

AddaPointLightGameObjectasachildtoourLaserobject.

SetthePointLightasshown:

LaserTrapPointLightsettings.

71

ThePointLightactsasthelaser’slightsourceandwillriseandfallwiththelaseritself.Thisgivestheillusionthatthelaserbeam,drawnbytheLineRenderer,isemittinglight.

TheLineRendererComponentTheLineRenderer,asitsnamesuggests,drawslinesin3DspacewithinourScene.Itcontainsanarraywhichdefinestheseriesofpointsthroughwhichthelinewillbedrawn.ThelineitselfisdrawnusingthesamerenderingtechniqueastheTrailRen­derercomponent,makingitidealforlasers,lightningandsoon.

Next,settheLaserTrapscript’sproperties,whichshouldbeasshown:

TheLaserTrapscript’sproperties.

TIP TheLaserHitassetcanbefoundinsidethePropsfolderintheProjectPane.

Finally,duplicate(CMD+DonMac,orCtrl+D),(orcreateaPrefab)ofourLaserTrapandplacesomeinthelevelasyouseefit.Isuggestplacingfourintotal,withtwoineachofthetunnels.

FeelfreetovarytheLaserTrapscript’spropertiesandexperimentuntilyougetaresultyouaresatisfiedwith.

72

TheLaserTrapScriptThekeytotheLaserTrapistheLaserTrapscript,solet’stakeacloserlook...

OverviewTheLaserTrapisaLineRenderercomponentwhichismovedupanddownbythescript.Thissamescriptalsochecksforcollisionsandtriggerstherequisitevisualeffectsshouldoneoccur.

First,wedefinesomebasicpropertiesforourlasertrap:

• heightdefinestheamplitudeoftheoscillation,dictatinghowfaraboveandbelowitsstartingpointthelaserbeamwilltravel;

• speeddefineshowfastthebeammoves;

• timingOffsetallowsustoseteachlasertrapobjecttostartatadifferentpointinitsoscillationcycle;

• laserWidthdefinesthewidthofthelaserbeamfromendtoend;

• damagedefineshowmuchdamagetheplayerwillsufferifherunsintothebeam.

• hitEffect,canbeusedtolinktoanarbitraryGameObject,whichwillbeinstantiatedwhensomethinghitsthelasertrap.Thisismoreflexiblethanhard­codingtheeffectintheLaserTrapobjectandmakesiteasiertore­usethisassetinotherprojects.

Thescriptdefinestwofunctions,solet’slookattheseinturn:

TheStart()functioninitializestheLineRenderercomponent,storingitsinitialloca­tion(intheYaxis)forlater,andsettingtheLineRenderer’ssecondvertextomatchthepositiondefinedbylaserWidth.Thisletsuseasilytweakthelengthofthebeamtomatchthewidthofthecorridor.

NOTE WecouldjustsettheendcoordinateofeachlinebyhandintheLineRen­derer’sarray,buthandlingthisinthescriptgivesusabitmoreflexibilityshouldwedecidetomakethelaser’sbeamanimatein­game.

NowwecometothemainUpdate()function.Thisiswheretheinterestingstuff

happens.

First,wehavethelaserbeammovementtocalculate.WejustusetheMathf.Sin()

functionforthis.WegrabthecurrenttimeusingTime.time,(whichreturnsthe

time,inseconds,sincethegamestarted),multiplyitbyourdesiredanimationspeedandaddinthetimingOffsetvalue.Thisgivesusourpositionalongthesinecurve.Fi­nally,wemodulateitbyourdesiredheightandusetheresultasanoffsetfromourbaseline.

73

Thenextstepistocheckforcollisions.ThescriptdoesthisbycastingarayalongthepathofthebeamandcheckingifithitsanyGameObjectwhichhaveColliderCompo­nentsattachedtothem.

(Thetime­basedtestistheretoallowtimeforareaction.Ifwedidn’tdothis,theplayerwouldloseahealthpointforeveryframeinwhichhe’shitbytheray­cast.)

Ifwedohitsomething,wecheckifit’saplayerorenemyand,ifso,sendthe“Apply­Damage”messagetoit.Atthesametime,wealsoinstantiatethehitEffectGameOb­ject,sothatitcanperformitsmagic.ThisistheLaserHitGameObject,whichproducesanenergybursteffect:

Beinghitbyalaserisbadforyourhealth.

Ifyouplaythetutorial,youshouldfindthatLerpzlosesahealthpointifhehitsthelaserbeam.

74

TheRobotGuardsThemobileantagonistsarerobotguards,placedstrategicallyaroundthelevel.WhenLerpzgetswithinrange,theseguardswillhomeinonhimandattempttocausehimharm.

Arobotguard.

Lerpz'senemiesinthisgamearerobotguards.Thisparticularmodelisnowconsidereda'classic',worthyofpreservation.Collectorsandenthusiastscomparethem,unfa­vorably,withsuchotherclassicgemsofyesteryearastheFordEdsel,AppleLisaandSinclairC5.

Thisparticularmodeliswell­knownforthewayitwascunninglydesignedtobesus­ceptibletoacoupleofwell­aimedpunchesatthetorso.Whensoincapacitated,therobotsejectanyitemstheyarecarrying,afterwhichtherobotwillhappilylieonthegrounduntiltheBIOScanreboot,althoughthiswouldonlytakeplacewhentheprox­imitysensorassertedthattherewasnothinginthevicinity.Thesesecurityrobotsarenowusedasgardenornaments.

Fromtheabove,wecandeterminethat:

• RobotswillhaveafairlyrudimentaryAI.

• Therobotsejectcollectableitemswhenknockeddown.

• Robotswillrespawnwhentheplayercan'tseethem.

75

Seek&DestroyMostgameAIisprimarilyfocusedonmodelingbehaviorratherthanintelligenceassuch.Ourrobotsdeliberatelydisplaylittleactualintelligence,butmerelyreactinpre­dictablewaystotheplayer'spresence.Thisisnotnecessarilyabadthing:Manygameplayerslikethiskindofbehavior.Itgivesthemapatterntolookforandthusmakesiteasierthelearnhowtodefeattherobots.

TherobotguardsthereforehaveaverysimplebehaviorpatternandthisisreflectedinthemainAIscript,EnemyPoliceGuy.

Idle­­Inthismode,theguardsjuststandtherethinkingrobotthoughts,tickingawayquietlyandwaitingforanintrudertocomeintorange.

Threaten­­Ifanintrudercomeswithinasetdistanceoftheguard,theguardcomesoutofstandbyandannouncesitsintentionswhilespinningitsbatoninwhatwasad­vertisedinthesalesbrochureasa“threateningmanner".

Seek&Destroy­­Onceactivated,therobotguardswillhomeinontheintruderandtryandattackhim.Iftherobotguardgetswithinstrikingrangeoftheintruder,theguardwillattempttohititwithitsbaton.

NOTE Oneanimationsequenceisusedforbothmodesandisnamed“turnjump”inthemodelandscript.

Struck­­ Iftheplayerstrikestherobotguard,thisanimationisplayed.(“gothit”inthemodelandscript.)

Iftheplayermovesoutsidetherobot'slimitedscanningrange,therobotwillrevertbackto"Idle"mode.

TheabovestatesarehandledbytheEnemyPoliceGuyscript,whichalsodealswithswitchingbetweentheanimationsequences.(Thereweren’tenoughanimationse­quencestojustifysplittingthescriptintotwoaswasdonewiththeplayer.)

AddingtheRobotGuardsWeneedafewoftheserobotsonourlevel,so...

OpentheProjectPaneandlocatetheCopperPrefabinsidetheEnemiesfolder.

FindasuitablespotonthelevelanddropthePrefabontotheScene,ensuringitisontheground.(ThePrefabincludesaCharacterControllerComponent.Makesurethelowerendofthecapsulecolliderisjusttouchingtheground,orslightlyaboveit.)

Areaswithfencesorwhichareotherwisemainlyenclosedarebestasthismakesitlesslikelythattherobotwillfalloffthescenery.

76

Ifyouplaythegamenow,youwillseetherobotstandingaroundplayingtheIdleanimation.Wenowneedtoaddsomescriptstomaketheserobotsdosomethingmoreinteresting.

Therearetwoscriptsfortherobotandwe'lladdthemdirectlytotheoriginalPrefab:

SelecttheCopperPrefabtobringitupintheInspector.

DragtheEnemyDamageandEnemyPoliceGuyscriptsontotheCharacterCon­trollercomponentintheInspector.

Ifyouhaven’talreadydoneso,dragtheThirdPersonCharacterAttackscriptontothePlayerobject.Thishandlestheplayer’sendofthepunchingmovement.(Lerpzwon’tpunchrobotswithoutit!)

Ifyouplaythegamenow,therobotshouldreacttoyouifyougettooclosetoit.

BlueSparksOfDeathIftheplayermanagestobeattherobotupandknockhimdown,adeathsequenceisinitiated.Wewanttherobottofalloverinashowerofsparksandliethereuntiltheplayermovesoutofrangebeforeresetting.

Inaddition,whentherobotdies,wewantittospitoutanycollectablesit’scarrying.

Ratherthanaddingyetmorescripting,animationdataandotherelementstoourcur­rentPrefab,we’llcreateanewonejustforthedeaththroesofourelectricfoe.

Divide&ConquerWhenourrobotdies,weneedittostopreactingtotheplayerandsuspendthescripts.Thisisnotquiteassimpleasitseems:scriptstendtorunindependentlyofeachother,sowewouldneedtosendoutmessagesandmaintainadedicatedstatevari­ableineachscriptwhichwouldneedtobecheckedduringeverycycle.

ItismucheasiertosimplyswapoutourrobotPrefabforanotherone­­avirtualstuntdouble,builtspecificallyforthepurposeoffallingover,withsuitablespecialeffectsandscriptingtospitoutuptotworandompickups.Sothisisexactlywhatwe’lldo.

77

Arobotguardkeelsover

TheimageaboveshowsthisreplacementPrefabinaction.

ThescriptwhichbringsitintoexistenceisEnemyDamage,solet’stakeacloserlookatit...

WhenanApplyDamagemessageissenttotheGameObject,theApplyDamage()

functioniscalled.Iftherobothassufferedtoomanyhits­­inourexample,thehitpointsaresetto3­­itcallstheDie()function,whichiswherethefunbegins.

TheDie()functionfirstmarksourCopperGameObjectfordestruction.(Thedestruc­

tionisnotperformeduntilaftertheUpdate()functionisdone,sothere’snoreason

nottodothisatthestartofthefunction.)

Next,thereplacementPrefabisinstantiatedandtherobot’scurrentposition­­includ­ingthoseofallchildobjectfromheadthroughtorsoandarms ­­ iscopiedover.

Thesparklyexplosionisjustanotherparticlesystemasset.ThisisinstantiatedandmadeachildofournewdyingrobotGameObjecttoensureitappearsintherightlo­cationandalignedcorrectly.

Thenextstep,nowthatwehaveourinstanceinplace,istogiveitapushawayfromtheplayersothatUnity’sphysicsenginecanmakeitfalloverandrollabout.

Finally,weinstantiateamaximumoftwopickups,eachwitha50:50chanceofbeingeitherahealthorfuelcellpickup,andlaunchthemintotheairinarandomdirection.

78

YoucanimplementalloftheabovebyselectingtheCoppergameobjectandalteringthevaluesinEnemyDamagecomponent.Weencourageyoutotryoutdifferentvariations.

DroppablePickups&PhysicsThepickupswhichappearwhenarobotisknockedoverarePrefabsderivedfromtheoriginalswecreatedearlier,butwithaddedparticleeffectsandascript,Droppa­bleMover,whichhandlesmovementandcollisions.

Thescriptisneededbecausethecolliderissetasatriggerforthepickupandthusisignoredbythephysicsengine.Thepickupisgivenaninitialvelocityandthescriptthenusesraycasting­­castinganimaginarylinedownwardstocheckifthepickuphashitasurface.Whenitdoes,thescriptissimplydisabled.(Thiscodedoeshaveonedrawback:itonlychecksdownwards,intheYaxis.Thus,thedroppablepickupitemmayendupembeddedpartwaythroughawall.)

Spawning&OptimizationSpawningenemieswhentheyarewithinasetdistanceoftheplayerisanoldtrickdatingrightbacktotheearliestcomputerandvideogames;itremovedtheneedtostoreeachenemy’sstate.Wecanalsousethistricktoreducetheloadontheproces­sor.Bysimplydeletingeachrobotifit’snotvisibletotheplayer,wecanavoidrunningscriptsandAIunnecessarily.

Let’sbegin:

CreateanemptyGameObjectatthetopleveloftheHierarchyPane.

RenameitCopperSpawn.

DragtheEnemyRespawnscriptontotheobject.

PositionourCopperSpawnobjectsomewherewhereyou’dliketoseearobotappear.(Youshouldseeasmallroboticondisplayedasagizmo,aswellasaspheretoshowthespawnrange.ThesearedrawnbytheEnemyRespawnscript.)

Adjusttheobject’ssettingsasshown.

79

CopperSpawnsettings.

We’llneedafewoftheseGameObjects,solet’ssetupaparentGameObjectandmakeourCopperSpawnobjectitschildren...

CreateanemptyGameObjectatthetopleveloftheHierarchyPane.

RenamethisnewobjectEnemies.

MakeourCopperSpawnGameObjectachildofEnemiesbymovingtheformerintothelatter.

NowbuildaPrefabusingourCopperSpawnGameObject.Positioninstancesofthesearoundthelevel.

NOTE Oneside­effectofthistechniqueisthat,ifyouhavekilledarobotandthenmovedoutofrange,itwillreappearasgoodasnewifyoureturntoitsloca­tion.

Howitworks.TheCopperSpawnGameObjectscontainascriptwhichchecksiftheplayerhascomewithinrangeand,ifso,createsaninstanceoftheCopperPrefab.Whentheplayerwalksoutsidethisrange,ourrobot­spawningscriptwillautomaticallyremovethero­botfromtheScene.

ThescriptwhichdoesthisisEnemyRespawn.Thisscriptisheavilycommented,butthetwomainfunctionsare:

Start()­­justcachesalinktotheplayerGameObject’stransformaswe’llneedtomesswithitlater.

Update()­­Firstchecksiftheplayer’sinrangeandinstantiatestherobotifso.If

not,andtheplayerhasjuststeppedoutofrange,therobotprefabisdestroyed.

TherearealsotwoGizmofunctionsusedintheEditor:

EnemyRespawnalsomakesuseofUnity's"Gizmos"feature.AGizmoisusuallyavis­ualaiddisplayedonlyintheSceneViewratherthanin­game,suchas,inthiscase,asphereshowingthespawnrange.Inthisscript,wehavetwotypesofgizmos:thefirst

80

drawsaroboticon,whichletsusselectthespawnobjectbyclickingonitsiconintheScenewiththemouse:

NOTE Theiconimageiscurrentlystoredinthe[PROJECT­PATH]/Assets/Gizmosfolder.

TheOnDrawGizmos()functionshowingtheroboticon.

TheGizmocodefortheiconisshownbelow:

function OnDrawGizmos (){ Gizmos.color = Color(1, 1, 1, 1); Gizmos.DrawIcon(transform.position, gizmoName + ".psd"); }

TheOnDrawGizmos()functioniscalledeverytimetheUnityeditorGUIisupdated

orrefreshed,sotheiconwillalwaysbevisible.Inorderthatthisfunctionknowswhichiconimagetouse,weneedto...

...settheGizmoNamepropertyintheInspectorto“Copper”.

Conversely,theOnDrawGizmosSelected()functioniscalledbyUnity'seditorGUIonlywhentheobjectisselected.Aslongasitisselected,itwillbecalledeverytimetheUnityeditorGUIisupdatedorrefreshed.

Inthisexample,thefunctiondrawsasphereusingspawnRangetodefineitsradius,thusprovidingavisualdisplayoftheareawithinwhichtheenemyrobotwillbein­stantiated;whentheplayermovesoutsidethissphere,therobotwillbeautomaticallydestroyed.

81

function OnDrawGizmosSelected (){ Gizmos.color = Color(0, 1, 1); Gizmos.DrawWireSphere(transform.position, spawnRange);}

TheOnDrawGizmosSelected()function showingthespheredefinedbytheSpawnRangevariable.

AlternativeOptimizationsInadditiontotheabovetechnique,UnityalsoofferstheOnBecameVisible()and

OnBecameInvisible()functions.However,unlikeourrespawningtechnique,theabovefunctionsarebasedonthecamera'sorientationandothersettingsratherthanthoseoftheplayerobject.ThismeansyouwillseeOnBecameInvisible()called

onanobjectjustbecausethecamerahasturnedawayfromit.Thismaynotbewhatyourequire.

Anothertechnique,evenmoreoptimalthanourown,istouseCollidercomponentsastriggersinsteadofusingscriptcodetocheckfortheplayer'slocation.UnityprovidestheOnTriggerEnter()andOnTriggerExit()functionsforthispurpose.How­ever,thismightnotbefeasibleifyouwantyourrespawnscriptstobeattachedtoanobjectthatneedstouseacolliderforotherpurposes.

82

Audio&FinishingTouches

IntroductionWhenyoubuyaCDorwatchamovie,thesoundyouhearhasinvariablybeenthroughanumberofstages,thelastofwhichisknownasmastering.Masteringistheartoflisteningtotheaudioasawholeandadjustingvolumelevels,filteringoutfre­quenciesandanynumberofothertechnicaltrickstomakethefinalmixsoundasgoodaspossible.Sometimestheprocessfixesobviousflawsinamusicalnumberorsoundtrack,suchasaninstrumentbeingtooloudortooharsh.Atothertimes,theprocessisjustusedtoensurethesoundtrackwillsoundgoodenoughonacheapTVloudspeakerwithoutcompromisingthesoundwhichwouldbeheardfromahigh­endhomecinemasystem.

Gamesarenodifferent:oncetheroughgameplayisinplace,itmakessensetospendsometimefine­tuningit,tweakingandadjustinguntilitisasgoodasitcanpossiblybe.

AudioFinalizingaudioandperformingmasteringdutiesisdifficultwithgames.Gamesareinteractiveratherthanpassive,linearformsofentertainment.Thismeansyouneedtoconsiderhowtheindividualaudioassetsinyourprojectwillinteractand,ifnecessary,performsomepreemptivemasteringandmixingyourself.Essentially,masteringisareal­timetaskwhichneedstobehandledwithinthegamelogicitself.

Inanidealworld,everydeveloperwouldhaveaccesstotheirowntameaudioengi­neer,butthisistherealworldandmanysmall­scaleprojectssimplycannotaffordthis.

Inthischapter,weaddsoundef­fectsandtwocut­scenestoourgame.

Themostimportantconsiderationisensuringeachsoundinyourprojectsitswellwithallitsfellows.Thisisasubjectiveprocessassomepeoplelikealotofbass,whileotherspreferhigher­frequencysoundsintheirsoundscapes,soit'sagoodideatousealphaandbetatesterfeedbacktogetamixofobjectiveviewsonyourproject'ssound.(Anaudiomonitoringsetupisalsouseful,butthesecanbeexpensiveandannoyingtocol­leaguesifyoucan’taffordadedicatedroomforthepurpose.)

Ideally,youneedtouseaudiofromasinglesource,mixedbythesamepairofears,soyougetaconsistentsound.If­­asinthistutorial'scase­­youmustuseabunchofdif­ferentaudiosources,bepreparedforalotoftweakingofsettingstogetthesoundstositjustrightinthegame'soverallmix.Anover­loudsoundeffectwillbeveryobvi­oustoplayersandcouldbecomeirritatingifitisheardfrequently.Play­testfrequentlyandbepreparedtobudgetsometimeforthisprocess.

SampleNotesUnityhasafairlysimpleinterfaceforaudio,buttherearesomeimportantthingstoconsider:

• Ensureyoursampleshavesimilarlevels.Thismakesvolumelevelsandroll­offsettingsmuchmoreconsistent.Normalizationcanhelphere,butyoushouldalsoconsiderhowyoursoundswillbemixedtogether.Toomanynoisysoundeffectswillconfusetheplayer.

• Usemonosamplesifyouneedthesoundtobepositionedrealisticallywithinthe3Dworld.

• CompresssamplesusingtheOggVorbiscompressionsystem.(Unitycandothisforyou.)

• Ifbuildingfortheweb,youshouldalwayscompressyoursoundsusingOggVorbis.

• Checkthe"Decompressonload"boxforshorter,frequently­usedsoundeffects.

• Onlyusestereosamplesforlongmusicalpieceswhichdonotneedtobepositionedspatiallywithinthescene.Suchsamplefileswillalwaysbeplayedbackastheyare,atthedefaultAudioListenervolume.

AddingSoundtoLerpzEscapes!Wehavealreadyaddedafewspoteffectstothegame.Inthischapterwewilladdsomemoreaudiotoourgameandcompletetheprocess.

Inmanygamegenres,soundeffectscanbeobtainedfromrealsources,ortakenfromasoundeffectslibrary.However,LerpzEscapes!needssomesoundeffectswhichcan­notbeobtainedsoeasily.Pointingamicrophoneatapassingspacecraftorconvenientrobotguardisnotanoption,sowewillneedtothinkcreativelyaboutthese.

84

Wewillnotaddeverypossiblesoundeffecttothegameinthischapter;onlyenoughtodemonstrateUnity'saudiofeatures.Onceyouhavereadthroughthischapter,youwillbeabletoaddadditionalsoundeffectsonyourown.

Thecompletelistofsoundeffectsthegameneedsis:

ThePlayer:• Walking/Runningsounds;

• Attackingsound;

• Gettinghitsound;

• Dyingsound;

• Jet­packthrusterssound;

TheRobotGuard:• Idlesound;

• Attackingsound;

• Gettinghitsound;

• Dying/Explodingsounds;

TheCollectables:• FuelCellcollectedsound;

• Healthcollectedsound;

Environmentalsounds:• Along,loopedsoundsampletogiveasenseofambience.

• Awhooshsoundforthejumppads.

SpaceshipImpoundFence:• "Active"sound;

• "Shutdown"sound;

TheSpaceship:• Atake­offsoundtoplaywiththecut­sceneanimation;

Wehavealreadyaddedafewsoundeffectstoourgame,butwestillneedtoaddoverfifteenmore.Someoftheseareobviousfoleysounds,suchastheplayer'sfootstepsandthejet­packthrusters.Someoftheothersamplesarerepurposedfoleysounds.(ThepickupFuelsound,forexample,isjustthesoundofagolfballbeingstruckbyagolfclub.)

85

Thesesoundscanbefoundinalmostanysoundeffectslibrary.Apple'sownGarage­Band,LogicStudio8andSoundtrack/SoundtrackProsoftwareincludesuchsoundsandtheselibrarieswerethesourceformostofthesoundeffectsinthistutorial.Manymoresuchsamplesareavailableonline­­oftenforfree!

AmbientSoundsOurgameissetinanenvironmentwayabovethecloudswithanimpressiveviewofnearbyplanets.Suchanexposedlocationneedsasuitablyout­of­this­worldsound­scapetoimprovethesenseofimmersioninthegame.

ThetutorialprojectincludesaloopedambientsoundsamplenamedsceneAtmos­phere.ThisisastereoOggVorbissamplecreatedbythrowingsomeold,monoBBCRadiophonicWorkshopsoundeffectsfromthe1960satGarageBand,andcombiningthemintosomethingthatsoundedsuitablyalien.

ThesampleisaddedtotheNearCameraobjectintheHierarchyPane.

DropthesampleontotheNearCameraobject.UnitywillautomaticallycreateanAudioSourcecomponent.

Adjustitssettingsasshownbelow:

AddingthesceneAtmospheresoundtotheNearCameraobject.

TheAudioSourcecomponentincludesbasiccontrolswhichallowustotellUnityhowtodealwiththesoundeitherthroughtheInspectororthroughscripting.

Inthisinstance,wesetthePlayOnAwakeswitchsothesoundstartsplayingauto­matically.Thevolumelevelisdeliberatelysetlowasthisisambient,backgroundsoundandwedon'twantittodistractfromtheothersoundsinthegame.

86

Let’stakeaquicklookatwhattheremainingsettingsmean:

ThePitchsettingdefineshowfastthesampleplays,with1beingthenormalspeed.Thealgorithmusedisabasicone,similartoataperecorder,sothereisnotime­stretchinginvolved;asettingof2herewouldplaythesampleattwiceitsnormalspeed,doublingitseffectivepitch.

TIP ThePitchsettingisusefulforone­shotsoundeffectsasyoucanadjustthevalueslightlywitheachplaybacktoaddvarietytothesounds.It’sidealforshorteffectslikegunshots,lasersandfootstepsandsaveshavingtocreatemultiplesamples.

Nextcomesettingswhichdefinethevolumerangeofthesound.Ifwewantedthecliptobeinaudiblewhenwe'realongwayfromitssource,MinVolumewouldbesettozero.

Asitsnamesuggests,MaxVolumeisthemaximumvolumeoftheaudio.Wecouldstandrightontopofthesoundsourceanditwouldn'tgetlouderthanthisvalue.

NextcomestheRolloffFactor,whichdetermineshowquicklythesound'svolumechangesrelativetothelistener'sdistance.Asmallervaluemeansthesoundwillbeaudibleoveralargerdistance.Thissettingiskeytoensuringrealisticauralbehaviorinthegame.

FinallywehavetheLoopsetting.Thisisenabledbecausewewanttheimpoundfence'ssoundtokeeponplayinguntilwetellittostop.

NOTE Itisimportanttoensureloopedsoundsaredesignedforcleanloopingoryouwillmostlikelyhearanaudibleclickorpopeachtimeplaybackisrestartedfromthebeginningofthesample.Mostsampleeditorsincludea"findzerocrossings"featureforthispurpose.

TheJumpPadsWhenwebuilttheJumpPadPrefab,wedidn’tbotherwithasoundeffect.Nowwe’lladdone.Buthowwillweplayitback?

Luckily,theJumpPad’sscriptalreadyhassupportforasoundeffect.Ifyouopenthescriptupintheeditor,you’llseetheplaybackcodehere:

... if (audio) { audio.Play(); }...Theaudiovariabledoesn’tappeartobedefinedwithinthescript,sowhere’sitcom­ingfrom?

87

It’scomingfromUnityitself.Thisisaconveniencevariable­­oneofanumberofsuchvariables­­whichsimplypointsattheAudioSourceComponentattachedtowhateverGameObjectthescripthappenstobeattachedto.Aswehaven’taddedoneoftheseComponents,theaudiovariablewillbenull,sothescriptskipstheplayback.

ClickononeoftheJumpPadinstancesinourScenetobringitupintheInspec­tor,

DragthejumpPadsoundeffectontotheInspector.(Thiswillautomaticallycre­ateanAudioSourcecomponentforus.)

ApplythechangetotheoriginalPrefab,soallourJumpPadscansharethesamesoundeffect.

ExperimentandtweaktheAudioSource’ssettingsuntilyou’rehappywiththesoundeffect.

CollectablesThecollectableitemsaretheeasiesttodealwith.ThesesoundeffectsarepickupFuelandpickupHealthforthefuelcellsandhealthcollectablesrespectively.Addingtheseeffectsissimplicityitself:thePickupscriptalreadyhassupportforaudio;wejustneedtodroptherelevantsoundsampleintothescript'ssoundeffectslotineachpickuptype'sPrefab.

TheimagebelowshowstheFuelCellPrefabInspectordetailsforoneoftheinstancesinthelevel,withthepickupFuelsoundeffectaddedtothePickupscript'sSoundvari­able:

88

SettingthefuelCellPrefab’ssoundeffect.

AddtheaudioeitherbydroppingthepickupFuelsoundeffectontotheSoundvariable'sslot,orbypickingthesoundeffectdirectlyfromthelistofavailablesampleswhichyoucanbringupbyclickingthesmalltriangletotherightoftheslot.

SettheSoundVolumeto2,tomakethesoundeffectstandout.

ApplythesameprocessfortheHealthpickuptoo,usingthepickupHealthsam­pleinstead.

TIP YoucanaddthesoundeffectdirectlytothePrefabtosavetime.

Ifyouplaythegamenow,eachcollectableitemwillnowplaytheappropriatesoundeffectwhenyoupickitup.

89

TheImpoundFenceTheforcefieldthatsurroundsthespaceshipshouldmakeahumming,fizzingnoisewhileitisactive.AsuitablesoundeffectwascreatedusingGarageBandbycombiningsomeloopedambienttexturestogetherandexportingtheresultingsoundtoafile.

TIP Apple'sGarageBandonlyexportstotheAAC(".M4A")soundformat,soAudacitywasusedtoconvertthistotheuncompressed,monoAIFFaudiofileusedintheproject.

ThesampleisnamedactiveFence.

GototheHierarchyPane,openlevelGeometryandfindtheimpoundFenceob­ject.

DroptheactiveFencesounddirectlyontothisobject.ThiswilladdanAudioSourcecomponenttotheimpoundFenceobject.

Finally,changetheAudioSource'ssettingstoreadasfollows:

ImpoundFenceAudioSourcesettings.

ThePlayerLerpzhimselfmakesnosoundsatthemoment.Addingsoundeffectsmakessense,butwhichones?

Thesoundeffectswewillimplementinthistutorialare:

• Apunchsound;

• A“struck”sound,playedwhenLerpzishitbyarobot;

• Asoundeffectforthejet­packthrusters;

90

• Asoundtoplaywhentheplayerdiesandisrespawned.

(Thefootstepeffectisleftasanexerciseforthereader.)

Thesesoundswillbeplayedbyourscripts.

Punching.ThepunchingmovementandanimationarehandledbytheThirdPersonCharacterAttackscript.ApropertyforthesoundeffectisexposedbythescriptintheInspector.SetthePunchSoundpropertyasshown:

LerpzPunchsoundeffect.

Thescriptplaysthissoundusingthefollowingcode:

if (punchSound) audio.PlayOneShot(punchSound);

Thisfirstchecksifapunchsoundeffecthasbeensuppliedtothescript.Ifso,itusesthePlayOneShot()functiontoplaythesound.Thisfunctioncreatesatemporary

GameObjectwithanAudioSourcecomponent,addsittotheSceneandplaysit.Whenthesoundeffectisfinished,theGameObjectisremovedfromthescene.

NOTE Whilethegameisrunning,youwillseetheseone­shotsoundsappearingbrieflyintheHierarchyPane.Thisisnormalbehavior.

Strucksound&ScreamsoundTheThirdPersonStatusscripthandlestwomoresoundeffects:theoneplayedwhenLerpzisstruckbyanenemy,andtheoneplayedwhenLerpzdies(justbeforeheis

91

respawnedorthegameends).Bothsoundeffectsarehandledthesamewayasthe“lerpzPunch”soundeffectweaddedearlier.

AddboththeLerpzStruckandLerpzScreamSFXsoundsasshownbelow:

AddingtheStruckSoundandDeathSoundeffects.

TheJet­packThejet­packisalooped,ratherthanaone­shot,soundeffect,sowe’lladdthesoundeffecttothePlayerGameObjectdirectlyasanAudioSourceComponent.TheJetPackParticleControllerscriptwillautomaticallycreateanemptyAudioSourcecomponent,butweneedtoaddtheaudiofiletoit:

DragthethrusterSoundaudiofileontotheAudioSourcecomponentinsidethePlayerGameObject.

EnsuretheAudioSourcesettingsareasshown:

92

Jet­packaudiosettings

Therearetwosectionsofscripttohandlethissoundeffect,bothofwhichareintheJetPackParticleControllerscript.ThefirstsectiongoesintheStart()functionand

initializestheAudioSourcecomponent:

audio.loop = false; audio.Stop();

Thesecondchunkisfurtherdowninthesamefunction:

if (isFlying) { if (!audio.isPlaying) { audio.Play(); } } else { audio.Stop(); }

TIP TheaudiovariableiscreatedbyUnityitself.ItisalwayssettopointattheAu­dioSourceComponentoftheGameObject.

93

Thiscodeisself­explanatory.TheneedtotestifthesoundeffectisalreadyplayingisbecausethePlay()functionwillalwaysstartplayingthesoundeffectfromthebe­ginning,regardlessofwhetheritisalreadyplaying.Thismeanswe’dhearastutteringsoundasUnitywouldrepeatedlyrestartthesoundsampleeverytimethePlay()

functioniscalled.

TheRobotGuardsTheseguyshaveanumberofscripts,someofwhichalsohaveaudiosamplesasprop­erties.Inaddition,eachrobotguardhasanAudioSourcecomponent.

UnlikethePlayer,whichusesitsAudioSourcecomponentsolelyforthejetpacksound,theEnemyPoliceGuyscriptusestheCopperPrefab’sAudioSourcecomponentformultipleloopingsounds,switchingbetweenthemasrequired.Anexampleofthecodeusedtoachievethisisshownbelow:

if (idleSound) { if (audio.clip != idleSound) { audio.Stop(); audio.clip = idleSound; audio.loop = true; audio.Play(); } }

TheaboveexamplecanbeseenatthetopoftheEnemyPoliceGuyscript’sIdle()

function.Thecalltoaudio.Stop()isimportanthereasswappingoutasamplewhileit’sbeingplayedcanhaveunpredictableresults.

Toaddthesoundeffects:

SelecttheCopperPrefabintheProjectPanetobringitupintheInspector.

DragtheCopperIdleLoopsoundeffectintotheAudioSourcecomponent.

AddthesameaudiofiletotheIdleSoundpropertyintheEnemyPoliceGuyscriptcomponent.

AddtheCopperActiveLoopsoundeffecttotheAttackSoundpropertyinthesamecomponent.

Finally,addtheMetalHitsoundeffecttotheStruckSoundpropertyoftheEn­emyDamagescriptcomponent.

(TheresultingviewintheInspectorPaneisshownonthenextpage.)

94

TheMetalHitsoundisplayedintheApplyDamage()functionwithintheEnemy­Damagescript.Thecodewhichdoesthisisshownbelow:

if (audio && struckSound) audio.PlayOneShot(struckSound);

AswiththeJetPack,theaudiovariableweusehereisactuallyashortcutvariable

createdbyUnityitself.ItistheequivalentofaGetComponent (AudioSource)

functioncall.TheupshotisthattheAudioSourcecomponentattachedtotheCopperPrefabisusedtoplaythesoundeffectforus,avoidingtheneedtoinstantiateatem­poraryaudiosourcecomponentforthepurpose.

NOTE ThesametrickisusedforsomeofthePlayersoundeffects.

CopperPrefabInspectorsettingsafteraddingtheaudiofiles.

95

CutScenesCut­scenesprovideausefulwaytofillintheplayeronaneventorstoryelementwhichtheyneedtoknow.Ideally,theneedtocutawayfromtheplayershouldbekepttoaminimum.Forthisreason,wehaveonlytwocut­scenes.

Thefirstoccurswhentheplayermanagestocollectalltherequiredfuelcellsinthelevel,thusunlockingthespaceship.Thiscut­sceneappearsusingapicture­in­picturetechnique,sothattheplayercancontinueplayingwhilethesequenceplaysout.

Oneextremelypragmaticreasonfornottakingoverthewholescreenwiththiscut­sceneisthatwe’dotherwisehavetofreezeallthegameelements­­robots,playercontrols,etc.­­whilethecut­sceneplays,becausetheplayerwouldbeeffectivelyblinduntilthescenecompletes.

NOTE Itisstillpossibletogetatthespaceshipifyouclimbontothenearbycrates,butthespaceshipisstilllockeddownatthispointandwon'ttakeoff.(Themeshcolliderisn’tchangedtoaTriggertypeuntiltheimpoundfenceisun­locked.)

Thesecondcut­sceneoccurswhentheplayertouchesthespaceshipafterthefencehasbeendisabled.Inthisscene,whichisplayedfull­screen,weseethespaceshiptakingoffandflyingofftofreedomandnewadventuresbeforeswitchingtotheGameOversequence.

Let’slookatthefirstsceneindetail...

UnlockingtheimpoundfenceWefirstcameacrosstheImpoundFencepartofthelevelwhenweaddedsomeani­mationtoit.Earlierinthischapter,weaddedasoundeffecttoitaswell.Nowwe’llanimateit.

Butfirst,weneedtomakesurewedothiswhenwe’vepickedupallourfuelcans(Youcanfindthecompleteassembledscriptcodelistedintheappendixsection):

OpentheThirdPersonStatusscriptandlookfortheFoundItem()function.

Aftertheblockofcomments,addthefollowinglinesofcode.(Makesureyouinsertthisbeforethefunction’sclosingbrace­­}­­symboloritwon’twork!)

if (remainingItems == 0) { levelStateMachine.UnlockLevelExit(); // ...and let our player out of the level. }

96

Savethescript.

OpentheLevelStatusscript.Thisiswhereboththecut­scenesarehandled(Youcanfindthecompleteassembledscriptcodelistedintheappendixsection).

Atthetopofthescript,addthefollowingcode:

var exitGateway: GameObject;var levelGoal: GameObject;

var unlockedSound: AudioClip;var levelCompleteSound: AudioClip;

var mainCamera: GameObject; var unlockedCamera: GameObject; var levelCompletedCamera: GameObject;

NOTE Theabovecodeincludesvariableswe’llneedforthesecondcut­scenetoo.

Next,addthefollowinglineofscriptcodetotheAwake()function:

levelGoal.GetComponent(MeshCollider).isTrigger = false;

Thatlineiscrucial.Itstopsthesecondcut­scenefromtriggeringprematurely.WiththeisTriggerswitchturnedoff,thespaceshipbecomesjustanotherpartofthesceneryaslongastheimpoundfenceisstillactive.

Nowfortheunlockingsequenceitself.

AddthefollowingfunctiontotheLevelStatusscript.(I’llexplainitaswego.)

function UnlockLevelExit(){ mainCamera.GetComponent(AudioListener).enabled = false;

Unitysupportsjustone­­andonlyone­­AudioListenercomponentinanyoneScene.UsuallythisisattachedtothemaincameraintheScene,butwe’reusingmultiplecamerasinourScene,soweneedtoensureweonlyhavetheoneAudioListenerac­tiveatanyonetime.Wewanttohearour“fencedisabled” soundeffects,sowe’llbrieflyenabletheAudioListeneronourcut­scene’scamerahere.

Next,weneedtoactivatethecut­scenecameraandenableitsAudioListenercompo­nent:

97

unlockedCamera.active = true; unlockedCamera.GetComponent(AudioListener).enabled = true;

TheImpoundFencehasaloopedsoundeffectattachedit.Weneedthatsoundtostopplayingnow:

exitGateway.GetComponent(AudioSource).Stop();

Nowwecanstartplayingour“fencedisabled”soundeffect:

if (unlockedSound) { AudioSource.PlayClipAtPoint(unlockedSound, unlockedCamera.GetComponent(Transform).position, 2.0); }

Withoursoundeffectstarted,wecanstarttheanimationsequence.We’lldothispro­cedurallybyusingscriptcodetoachievetheanimation.Thenextfewlinesperformthissequence.Thefirstlineaddsadelaybeforethesequencebeginstogivetheap­pearanceofthecut­scenetimetoregisterontheplayer’sconsciousness.(I’veleftthecommentsinplacesoyoucanfollowtheanimationsequence):

yield WaitForSeconds(1); exitGateway.active = false; // ... the fence goes down briefly... yield WaitForSeconds(0.2); //... pause for a fraction of a second... exitGateway.active = true; //... now the fence flashes back on again... yield WaitForSeconds(0.2); //... another brief pause before... exitGateway.active = false; //... the fence finally goes down forever!

Wenowhaveaccesstotheship!Allweneedtodonowismakethespaceship’sMeshCollidercomponentaTriggerratherthananormalcollider:

levelGoal.GetComponent(MeshCollider).isTrigger = true;

98

Finallywepauseafewmoresecondssotheplayerhastimetoseetheresults,beforeshuttingoffourcut­scenecameraandrestoringtheAudioListenercomponentonourNearCamera:

yield WaitForSeconds(4); // give the player time to see the result. // swap the cameras back. unlockedCamera.active = false; // this lets the NearCamera get the screen all to itself. unlockedCamera.GetComponent(AudioListener).enabled = false; mainCamera.GetComponent(AudioListener).enabled = true;}

Creatingthecut­scenecameraitselfisournexttask.ThisisjustanotherCameragameobject,exactlyliketheonewe’vebeenusinginthegameitself.Let’ssetitup:

AddanewCameratotheScene.

RenameitCutSceneCamera1

AddtheSmoothLookAtscripttotheCamera.

DropalinktothespaceShipmodelontotheSmoothLookAtscriptsothescriptknowswhatthecameraneedstopointat.

Settheremainingpropertiesasshown:

99

CutSceneCamera1properties.

Thiscamerashouldbesettolookatthespaceshipimpoundlot,clearlyshowingthefencing.Thesettingsshownaboveshouldbeagoodapproximation,butfeelfreetotweakit:

PositioningCutSceneCamera1.

Next,adjusttheCutSceneCamera1Camerasettingslikeso:

100

CutSceneCamera1CameraComponentsettings.

Thecameraneedstobedisabledbydefault.It’llbeenabledbyourscripts,butwedon’twantitrenderinganythinguntilthescriptsaysso.Disablingthecameraissim­ple:justunchecktheboxnexttoCutSceneCamera1,rightatthetopoftheInspector.

Also,takecaretosettheNormalizedViewPortRectsettingsasshownabove.Thesedefinethecamera’soutput’spositiononthescreensothatitappearsinthetoprightcornerofthedisplay.Thecamera’sdepthisalsosetto10,whichishigherthanthatoftheNearCamerasothecut­scenewillappearontopofthemaingameimagery.

Weneedtotestthesequence,sosettheLevelStatusscript’spropertiesasshownbe­low:

SettingtheLevelStatusscriptproperties.

101

ThespaceShipobjectisthespaceshipmodelsittinginsidetheimpoundfence.

TIP I’vetemporarilysettheItemsNeededvalueto2inthescreenshotabove.Thisletsmetestthecut­scenebycollectingonlytwofuelcansinsteadofwastingtimerunningaroundmostofthelevelfirst.Remembertoresetittoahighernumber­­say,20­­whenwe’redone.

Ifyouplaythegamenow,youshouldseethecut­sceneappearasshownbelow.

Ourfirstcut­sceneinaction.

NOTE Theframes­per­secondcountervisibleinboththemainviewandthecut­sceneinsertiscoveredinthechapteronoptimization.

Thelastcut­sceneisalittlemorecomplex.Weneedthespaceshiptotakeoffandflyaway.Wecoulddothisusingscripting,butit’smucheasiertouseanAnimationClipforthisinstead:

ClickonthespaceShipmodelinsidetheimpoundfencetoselectit.(OrclickonitintheHierarchyPane)

NowdragtheShipAnimationAnimationClipfromtheAnimationsfolderintheProjectViewandaddittotheSpaceshipAnimationpropertyoftheAnimationcomponentintheinspector.

Youwillnowseeananimationcomponentinthespaceshipinspector.Ifyouhitplaynowyouwillseetheshiptakingoff.Howeverweonlyneedtheshiptotakeoffwhentheleveliscomplete.Wewilldothiswithascriptinaminute.HoweverUnity,byde­fault,assumesyouwantanimationstoplayautomatically.Wedon’twantthis,so,withthegamestopped:

Disablethe“PlayAutomatically”checkboxofthespaceShipAnimationcompo­nent.

102

ThespaceShipobject’sanimationsettings.

Thenextstepistocreateoursecondcut­scenecamera:

CreateanewCameraobject.

RenameitCutSceneCamera2.

Positionitontopoftheimpoundlot’sofficebuilding,asshown:

103

PositioningCutSceneCamera2.

Don’tworryaboutthedirectionit’sfacing:we’reonlyinterestedinitslocation.Thescriptwe’llattachtoitwilltakecareoftherest.

AddtheSmoothLookAtscripttotheCamera.

DropalinktothespaceShipmodelontotheSmoothLookAtscriptsothescriptknowswhatthecameraneedstopointat.

AswithCutSceneCamera1,wealsoneedtoensurethisoneisdisabledbydefault,buttheremainingsettingsarealittledifferent,asyoucanseefromthescreenshotonthenextpage.Thekeydifferenceisthatthiscameraneedstotakeoverthewholescreen,ratherthanappearinginthecorner,sotheNormalizedViewPortRectpropertiesaresettotakethisintoaccount.

104

SettingsforCutSceneCamera2.

Nowforthecut­sceneitself.Thisisalittletrickierthanourfirstcut­scenebecausethemessagesusedtotriggerthesceneneedtoberelayedalongachain:

TheinitialtriggerhappenswhenthePlayertouchesthespaceShipmodel.(Ifthefirstcut­scenehasplayedthrough,thespaceShipmodelisnowactingasatriggerinsteadofasolidobject.)

ThespaceShipmodelthereforeneedsascriptattachedtoittodealwiththistriggerevent:

CreateanewJavaScriptscriptasset.

RenameitHandleSpaceshipCollision.

Addthefollowingcodetoit(Youcanfindthecompleteassembledscriptcodelistedintheappendixsection):

private var playerLink : ThirdPersonStatus;

function OnTriggerEnter (col : Collider) { playerLink=col.GetComponent(ThirdPersonStatus); if (!playerLink) // not the player. { return;

105

} else { playerLink.LevelCompleted(); } }

Alltheabovecodedoesischeckiftheplayerhastouchedthespaceshipand,ifso,calltheLevelCompleted()functioninthePlayer’sThirdPersonStatusscript.

AddthenewscripttothespaceShipobject.

TheThirdPersonStatusscript’sLevelCompleted()functionisevenshorterand

doessomethingverysimilar.

AddthefollowingfunctiontotheThirdPersonStatusscript:

function LevelCompleted(){ levelStateMachine.LevelCompleted();}

levelStateMachineisapropertywhichlinkstotheLevelStatusscript.LevelStatusiswheretheactionisasthelevelcompletionanimationissomethingonlythelevel­relatedscriptsshouldknowabout.

AddthefollowingLevelCompleted()functiontoLevelStatusnow(I’llex­

plainitaswego):

function LevelCompleted(){

First,wehavetodothesameAudioListenerswitchoveraswedidforourfirstcut­scene:

mainCamera.GetComponent(AudioListener).enabled = false; levelCompletedCamera.active = true; levelCompletedCamera.GetComponent(AudioListener).enabled = true;

Next,wewanttogivetheillusionthattheplayerisinsidethespaceship,sowe’llhide

106

him.Todothis,we’llsenda“HidePlayer”messagetothePlayer’sThirdPersonControl­lerscript.Thefunctiondisablestherenderingoftheplayer,sohebecomesinvisible:

playerLink.GetComponent(ThirdPersonController).SendMessage("HidePlayer");

Justtobeonthesafeside,we’llalsophysicallyrelocatetheplayertoapositionweknowshouldbesafe.(Therobotsdon’tcheckiftheplayer’svisibleornotandthey’restilloperating.)Inthisinstance,we’lljustmovetheplayer500unitsstraightup,whichshouldbefarenoughawayfromanyimmediatedanger.

playerLink.transform.position+=Vector3.up*500.0; // just move him 500 units

Next,we’llstartthelevelcompletedsoundeffect.Inthiscase,it’sthesoundofthespaceshiptakingoff:

if (levelCompleteSound) { AudioSource.PlayClipAtPoint(levelCompleteSound, levelGoal.transform.position, 2.0); }

Nowwestartthetimeline­basedanimationwerecordedearlierandwaitforittofin­ish:

levelGoal.animation.Play(); yield WaitForSeconds (levelGoal.animation.clip.length);

Andfinally,weloadthe“GameOver”Scene:

Application.LoadLevel("GameOver"); //...just show the Game Over sequence. }

Nextwemustensurethatthespaceshipmodelisnotsetasatriggerwhenourlevelstarts,andalsoensureplayerLinkispointingatthePlayerGameObject.We’lldefinetheplayerLinkvariableinsideanAwake()function,whichwillbecalledautomati­callybyUnitywhenthescriptisloaded.

107

ChangetheAwake()functiontomatchthescriptbelow.ItshouldbelocatedjustabovethefirstfunctioninLevelStatus.

private var playerLink: GameObject;

function Awake(){ levelGoal.GetComponent(MeshCollider).isTrigger = false; playerLink = GameObject.Find("Player"); if (!playerLink) Debug.Log("Could not get link to Lerpz"); levelGoal.GetComponent(MeshCollider).isTrigger = false; // make very sure of this!}

Finallyweneedtosetourupdatedscript’spropertiesasshown:

ThefinalsettingsfortheLevelStatusscriptproperties.

Theresult,whenyoucollectallthefuelcansandjumpintothespaceship,shouldlooksomethinglikethis:

108

Missioncompleted!Ourherotakesofffornewadventures.

Thenextchapterwrapsthingsupwithalookatoptimizationtechniques.

109

Optimizing

We’renowatthewrapping­upstage.Optimizingissomethingdoneneartheendofaproject,onceallthekeyelementsareinplaceandnaileddown.What,whereandhowyouoptimizeyourprojectverymuchdependsonyourproject’sdesignandcon­tent,sothischapterisprimarilyadiscussioncoveringthemorecommontypesofop­timization.

WhyOptimize?Unityprojectsareoftentargetedatoldercomputersthan,say,amainstream,big­budgetgame.Puzzlegames,casualtitlesandotherprojecttypesmayberequiredtorunonanythingfromaG4iBookwithamere256MbofRAMandanancientgraphicschipset,throughtoacurrentIntel­basedMacProstuffedtothefaceplateswithmem­oryandahigh­endgraphicscard.

Forthisreason,weneedtoconsideroptimizingourprojectsforfinalrelease.Inourcase,we’vealreadyoptimizedtheenemyrobotsbymakingthempopintoexistenceonlywheninrange,sothat’salreadycovered.However,therenderingofthescenecanbequiteslowandthisisworthlookingatinmoredepth.

OptimizingRendering:MonitoringFramesPerSecondThebestwaytodeterminewhetheryourgameneedstobeoptimizedistofindouttheframe­rate­­thenumberofimagesbeingrenderedeachsecond­­anddisplaythis.Thelowerthenumber,theslowerthegameisrunning.

Youmayhavenoticedanumber,ortheletters“FPS”,insomeofthescreenshotsinthistutorial.Thisisbecauseoneimportant,butmercifullyveryshort,scriptisrunning

GamesmayberequiredtorunonanythingfromaG4iBooktoacur­rentIntel­basedMacProstuffedwithmemoryandahigh­endgraph­icscardortwo.Forourgamestorunonboth,weneedtoopti­mize...

whichwe’lllookatnow:aFrames­per­secondreporterscript.Thescriptisnamedsim­plyFPSandcanbefoundintheScripts­>GUIfolderoftheProjectPane.

Thescriptisthoroughlydocumented,soIwon’tcoveritindetailhere,otherthantoaddthatitrequiresaGUITextComponent.(ThisisaUnity1.xGUIComponenttypeasopposedtothenewUnity2GUI.)

WiththisFPScounter,itiseasiertogetagoodideaofwhereoptimizationsneedtobemade.

NOTE TheFPSscriptisoflimitedvaluewhenrunningthegameintheUnityEditor.ThisisbecausetheEditorrenderermaybelockedtotheframe­rateofthedis­play.ItalsohastoupdatetheSceneViewandgenerallyperformsmoreerror­checkingwhileplayingScenes.Formoreaccurateresults,useaStandaloneBuildofyourproject.

MakingsenseoftheStatsdisplayNewtoUnity2isa“Stats”buttonabovetheGameView.Ifweenablethis,wecangetsomeadditionalmetricsonourgame,whichcanhelpdeterminewhetherthereareissuesofpolygon­countorotherobjectcomplexitytoresolve.

TheStatspanelinaction.

Thesestatisticsarebasedonwhatthecameraisrendering,somovingaroundthescenewillchangemanyofthestatistics.Theimportantelementsare:

DrawCalls­­thenumberofrenderpasses.Elementsinascenemayneedtoberen­deredmultipletimes:forshadows,multipleCameras,renderingtotextures,pixellights,andmore.Complexshaderscanalsocauseadditionaldrawcalls,particularlyifreflectionorrefractionisbeingcalculated.

111

Tris­­ Thenumberoftrianglesbeingdrawn.

All3Dmodelsarebuiltupfromtriangles.Thefewerthetriangles,thefasterthey’llrender.Round,curvedobjectswillgenerallyusemoretrianglesthanbasic,straight­edgedshapeslikecubesandplanes.

Verts­­ Thenumberofverticesbeingsenttothegraphicschip.

Avertexisapointin3Dspace.Themoreverticesyoucanshareacrosstriangles,themoreofsaidtrianglesyoucanrenderandthusthemorecomplexyourmodelscanbe.

UsedTextures­­thenumberoftexturesusedtorenderwhatyousee.

Materialscanuseoneormoretextures,dependingonhowthematerialisdefinedandtheshaderscriptitisusing.Theshaderdefineshowthetexturesarecombined,producingeffectssuchasbump­mapping,glossyshinehighlights,reflectionsandre­fraction.

TIP Particlesystemsusetwotrianglesforeachparticle,andatleastonetexture.(Texturesareusuallysharedbyalltheparticlesinaparticlesystem.)Itisveryeasytogetcarriedawaywiththeseeffects,butyoushouldtakecarenottooverdoit.

RenderTextures­­thenumberofcamerasoutputtingtoatextureratherthandirectlytothedisplay.Thisisn’tasclear­cutasyoumightexpect...

Rendertexturesareusedtoachieveanumberofeffects,suchaspost­processingef­fects,aCCTVscreendisplayinganotherareaofalevel,ortoproducereflectioninwa­ter,amirrororglassrefractioneffects.Inaddition,mostshadowsarealsoproducedusingthistechnique,soyouwon’tnecessarilyseeadditionalcamerasinyourSceneView.

OptimizingRendering:TheTwo­CameraSystemIfyouhaveplayedwiththecompletedproject,youmayhavenoticedbynowthatthereare,infact,twoNearCameras:aNearCameraandaFarCamera.Thistwo­camerasystemreducestheamountofrenderingneededforeachframe.

TheNearCamerarenderseverythingwithinacloserange;inthiscase,from0.4to50units.

TIP Aunitcan,intheory,beanyarbitrarylengthyoulike,butmostdeveloperstendtostickwith"1unit=1meter"forthesakeofsanity.Animportanttipistomakesureyourartist(s)areawareofthescaleyouareusing.

TheFarCamerarendersfromthe50unitsmarkthroughtoaround500units.How­ever,itonlyrendersasubsetoftheScenedata.ThesubsetisdefinedbyLayers.Each

112

objectintheSceneisgivenaLayertoliveon.TheNearCamerarendersallelementsregardlessoftheirlayer,butasyoucanseefromthescreenshotbelow,theFarCam­erahasbeentoldtoignorethoseitemsinthe“cameraTwo”or“cameraTwoIgnore­Lights”Layers.We’dalsolikeittoignorethefuelcansandhealthpickups.Thesehavethe“noShadow”Layer,sothattooisuncheckedforthiscamera.

NOTE Uncheckingthe“noShadow”Layeralsomeansitwon’trendertheplayerei­ther,whichisfineastheplayerisunlikelytobethatfarawayfromtheNearCamera.

TheFarCameraCullingMasksettings.

NOTE TheFarCameraisalsothecamerawheretheSkyboxisrendered.(SeetheClearFlagssettingintheshotabove.)TheNearCamera’sClearFlagssettingis“DepthOnly”,sothatitscontentissuperimposedoverthatoftheFarCamera.TheFarCamera’scontentisrenderedfirst.

ThisselectionisdefinedbytheCullingMaskpropertyofthecameracomponent.Tickedlayersarerendered;untickedlayersarenot.YoucandefineaLayerintheIn­spectorandassignonetoanyGameObject.

Youcanseethisoptimizationinactionwiththerobotguardsandthecollectableitems.Ifyoumovetowardsoneoftheseitems,youwillseethesceneryarounditisalwaysrenderedwhiletheitemitselfappearsrelativelyclosetotheplayer.

113

Endoftheroad.

TheRoadLessTravelledAtthetimeofwriting,thistutorialholdsthedubioushonorofbeingthelongesteverproducedforUnity.Wehaveseenhowtobuildasinglelevelofagamebasedaroundthe3DPlatformergenre,butevenafterallthesepages,wehavebarelyscratchedthesurfaceofwhatispossiblewithUnity2,orevenwiththisparticulargenre.

Ourjourneytogetherisdone:It’stimeforyoutotakethestabilizerwheelsoffthebicycleandcontinuealone,butbeforeyougo,herearesomesuggestionsforwhattotrynext...

SuggestedImprovementsLerpzEscapeshasbeenleftdeliberatelyunfinished.WehaveaverybasicStartMenuandaGameOverscreen,butthere’sonlytheonegamelevelanditisclearlyintendedtobethelastoneinthegame.Howcoulditbeimproved?

FixingthedeliberatemistakesYes,therearesomeminorissueswiththegameasitstands.Thesehavebeendeliber­atelyleftinplacetogiveyouachancetohoneyourskills.Theyare:

• Ifyoukillarobot,butarerespawnednearbybeforeyouhavemovedoutofrange,anewrobotwillappear,buttheoldonewillremain.

• TheLaserTrapsdon’tkicktheplayeraway,soitispossibletoloseallyourhealthratherquickly.

Whatwehavelearned.

Wheretogonext.

Bothcanberesolvedbyapplyingwhatyouhavelearnedinthistutorial.

MorelevelsTheProjectPaneincludesa“BuildYourOwn”foldercontainingalltheindividualas­setsusedtobuildthelevel,soaddingnewlevelsshouldnotbedifficult.

YouwillneedtousetheDontDestroyOnLoad()functionsothatyoucancarry

gamestateinformationbetweenthelevelssuchasthecurrentscore,livesremaining,etc.

MoreenemiesThegameonlyhastheoneambulatoryenemyintheformoftherobotguards.Whynotaddsomemore?ThisisagoodwaytoensureyouhaveunderstoodtheanimationandAIaspects.ItwillalsohelpyougetafirmgraspofbuildingmodelsandimportingthemintoUnity.

AddscoringLerpzEscapeslacksascoresystem.Addingoneisnotdifficult,butaddingvisualef­fectswhentheplayerdoessomethingworthyofincreasingtheirscorecanbeaschal­lengingasyoulike.(And,ofcourse,you’llwanttokeeptrackofthescoreacrossScenes.)

Addanetworkedhigh­scoresystemTheUnity2bringssolidnetworkingsupporttothetable,aswellasintegrationwithwebsites.Whatbetterwaytoshowoffthanuploadingyourhighscoretoacentralserversoyoucangloat?Thisisagoodwaytowrapyourheadaroundnetworkingba­sics.

AddmultiplayersupportAddingnetworkedmultiplayergamesupportisprobablythetrickiestthingyoucandowithanygame.Naturally,Unitycanhelpheretoo,butyouwillneedtogetdownanddirtywithscripting.Thisisagood,advanced­levelimprovementtoadd.

FurtherReadingThefirstplacetolookformoreinformationis,asalways,Unity’sowndocumentation.

Therearealsomanytutorials(includingvideoprimers)ontheUnitywebsite:http://unity3d.com/support/documentation/

Inaddition,theUnifyWikiisanexcellentsourceofuser­contributedinfo:http://www.unifycommunity.com

Andfinally,youcantalktoexpertsandnewcomersalikeinourthrivingforums,here:http://forum.unity3d.com/

115

ScriptAppendix

StartMenuGUIscript

HereistheassembledcodefortheStartMenuGUIscript.

//@script ExecuteInEditMode()

var gSkin : GUISkin;var backdrop : Texture2D;private var isLoading = false;

function OnGUI(){ if(gSkin) GUI.skin = gSkin; else Debug.Log("StartMenuGUI : GUI Skin object missing!");

var backgroundStyle : GUIStyle = new GUIStyle(); backgroundStyle.normal.background = backdrop; GUI.Label ( Rect( ( Screen.width - (Screen.height * 2)) * 0.75, 0, Screen.height * 2, Screen.height), "", backgroundStyle);

GUI.Label ( Rect( (Screen.width/2)-197, 50, 400, 100), "Lerpz Escapes", "mainMenuTitle");

116

Wherethescriptsareassembled

if (GUI.Button( Rect( (Screen.width/2)-70, Screen.height -160, 140, 70), "Play")) { isLoading = true; Application.LoadLevel("TheGame"); }

var isWebPlayer = (Application.platform == RuntimePlatform.OSXWebPlayer || Application.platform == RuntimePlatform.WindowsWebPlayer); if (!isWebPlayer) { if (GUI.Button( Rect( (Screen.width/2)-70, Screen.height - 80, 140, 70), "Quit")) Application.Quit(); }

if (isLoading) { GUI.Label ( Rect( (Screen.width/2)-110, (Screen.height / 2) - 60, 400, 70), "Loading...", "mainMenuTitle"); }}

GameOverGUIHereistheassembledcodeforfortheGameOverGUIscript:

@script ExecuteInEditMode()

var background : GUIStyle;var gameOverText : GUIStyle;var gameOverShadow : GUIStyle;

var gameOverScale = 1.5;var gameOverShadowScale = 1.5;

function OnGUI(){ GUI.Label ( Rect( (Screen.width - (Screen.height * 2)) * 0.75, 0, Screen.height * 2, Screen.height), "", background);

GUI.matrix = Matrix4x4.TRS(Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameOverShadowScale); GUI.Label ( Rect( (Screen.width / (2 * gameOverShadowScale)) - 150, (Screen.height / (2 * gameOverShadowScale)) - 40, 300, 100), "Game Over", gameOverShadow);

117

GUI.matrix = Matrix4x4.TRS(Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameOverScale); GUI.Label ( Rect( (Screen.width / (2 * gameOverScale)) - 150, (Screen.height / (2 * gameOverScale)) - 40, 300, 100), "Game Over", gameOverText);}

GameOverScriptHereistheassembledcodefortheGameOverScriptscript:

function LateUpdate (){ if (!audio.isPlaying || Input.anyKeyDown) Application.LoadLevel("StartMenu");}

ThirdPersonStatusHereistheassembledcodefortheThirdPersonStatusscript:

// ThirdPersonStatus: Handles the player's state machine.// Keeps track of inventory, health, lives, etc.

var health : int = 6;var maxHealth : int = 6;var lives = 4;

// sound effects.var struckSound: AudioClip;var deathSound: AudioClip;

private var levelStateMachine : LevelStatus; // link to script that handles the level-complete sequence.

private var remainingItems : int; // total number to pick up on this level. Grabbed from LevelStatus.

function Awake(){ levelStateMachine = FindObjectOfType(LevelStatus); if (!levelStateMachine) Debug.Log("No link to Level Status"); remainingItems = levelStateMachine.itemsNeeded;}// Utility function used by HUD script:

118

function GetRemainingItems() : int{ return remainingItems;}

function ApplyDamage (damage : int){ if (struckSound) AudioSource.PlayClipAtPoint(struckSound, transform.position); // play the 'player was struck' sound.

health -= damage; if (health <= 0) { SendMessage("Die"); }}

function AddLife (powerUp : int){ lives += powerUp; health = maxHealth;}

function AddHealth (powerUp : int){ health += powerUp; if (health>maxHealth) // We can only show six segments in our HUD. { health=maxHealth; } }

function FoundItem (numFound: int){ remainingItems-= numFound;

if (remainingItems == 0) { levelStateMachine.UnlockLevelExit(); // ...and let our player out of the level. }}

function FalloutDeath (){

119

Die(); return;}

function Die (){ // play the death sound if available. if (deathSound) { AudioSource.PlayClipAtPoint(deathSound, transform.position); } lives--; health = maxHealth; if(lives < 0) Application.LoadLevel("GameOver"); // If we've reached here, the player still has lives remaining, so respawn. respawnPosition = Respawn.currentRespawn.transform.position; Camera.main.transform.position = respawnPosition - (transform.forward * 4) + Vector3.up; // reset camera too // Hide the player briefly to give the death sound time to finish... SendMessage("HidePlayer"); // Relocate the player. We need to do this or the camera will keep trying to focus on the (invisible) player where he's standing on top of the FalloutDeath box collider. transform.position = respawnPosition + Vector3.up;

yield WaitForSeconds(1.6); // give the sound time to complete. // (NOTE: "HidePlayer" also disables the player controls.)

SendMessage("ShowPlayer"); // Show the player again, ready for... // ... the respawn point to play it's particle effect Respawn.currentRespawn.FireEffect ();}

function LevelCompleted(){levelStateMachine.LevelCompleted();}

LevelStatusHereistheassembledcodefortheLevelStatusscript:

120

// LevelStatus: Master level state machine script.var exitGateway: GameObject;var levelGoal: GameObject;var unlockedSound: AudioClip;var levelCompleteSound: AudioClip;var mainCamera: GameObject;var unlockedCamera: GameObject;var levelCompletedCamera: GameObject;

// This is where info like the number of items the player must collect in order to complete the level lives.

var itemsNeeded: int = 20; // This is how many fuel canisters the player must collect.

private var playerLink: GameObject;

// Awake(): Called by Unity when the script has loaded.// We use this function to initialise our link to the Lerpz GameObject.function Awake(){ levelGoal.GetComponent(MeshCollider).isTrigger = false; playerLink = GameObject.Find("Player"); if (!playerLink) Debug.Log("Could not get link to Lerpz"); levelGoal.GetComponent(MeshCollider).isTrigger = false; // make very sure of this!}

function UnlockLevelExit(){ mainCamera.GetComponent(AudioListener).enabled = false; unlockedCamera.active = true; unlockedCamera.GetComponent(AudioListener).enabled = true; exitGateway.GetComponent(AudioSource).Stop(); if (unlockedSound) { AudioSource.PlayClipAtPoint(unlockedSound, unlockedCamera.GetComponent(Transform).position, 2.0); } yield WaitForSeconds(1); exitGateway.active = false; // ... the fence goes down briefly... yield WaitForSeconds(0.2); //... pause for a fraction of a second... exitGateway.active = true; //... now the fence flashes back on again... yield WaitForSeconds(0.2); //... another brief pause before... exitGateway.active = false; //... the fence finally goes down forever! levelGoal.GetComponent(MeshCollider).isTrigger = true; yield WaitForSeconds(4); // give the player time to see the result.

121

// swap the cameras back. unlockedCamera.active = false; // this lets the NearCamera get the screen all to itself. unlockedCamera.GetComponent(AudioListener).enabled = false; mainCamera.GetComponent(AudioListener).enabled = true;}

function LevelCompleted(){ mainCamera.GetComponent(AudioListener).enabled = false; levelCompletedCamera.active = true; levelCompletedCamera.GetComponent(AudioListener).enabled = true; playerLink.GetComponent(ThirdPersonController).SendMessage("HidePlayer"); playerLink.transform.position+=Vector3.up*500.0; // just move him 500 units if (levelCompleteSound) { AudioSource.PlayClipAtPoint(levelCompleteSound, levelGoal.transform.position, 2.0); } levelGoal.animation.Play(); yield WaitForSeconds (levelGoal.animation.clip.length); Application.LoadLevel("GameOver"); //...just show the Game Over sequence.}

HandleSpaceshipCollisionHereistheassembledcodefortheHandleSpaceshipCollisionscript:

function OnTriggerEnter (col : Collider){ playerLink=col.GetComponent(ThirdPersonStatus); if (!playerLink) // not the player. { return; } else { playerLink.LevelCompleted(); }}

122

top related