View
4.010
Download
6
Category
Tags:
Preview:
Citation preview
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 21TheJetPack 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 51TheIngameHUD 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:TheTwoCameraSystem 112
8. Endoftheroad.TheRoadLessTravelled 114SuggestedImprovements 114Fixingthedeliberatemistakes 114Morelevels 115Moreenemies 115Addscoring 115Addanetworkedhighscoresystem 115Addmultiplayersupport 115FurtherReading 115
9. ScriptAppendixStartMenuGUIscript 116GameOverGUI 117GameOverScript 118ThirdPersonStatus 118LevelStatus 120HandleSpaceshipCollision 122
Introduction
Withitsmyriadfeatures,includingheightmappedterrains,nativenetworkingsupport,completephysicsintegrationandscripting,Unitycanbedauntingfornewcomers,butmasteringitsmanytoolsisinfinitelyrewarding.
Thistutorialwillwalkyouthroughtheprocessofbuildingacomplete3Dplatformgamelevelwithathirdpersonperspectiveview.Thisincludeseverythingfromplayercontrols,collisiondetection,someadvancedscripting,blobshadows,basicAI,addingagameHUD,cutscenesandaudiospoteffects.
WhatyouwilllearnThistutorialfocusesonthetechnicalsideofbuildingagameinUnity,coveringthefollowing:
• CharacterControllers
• Projectors
• AudioListeners,AudioSources&AudioClips
• MultipleCameras(andhowtoswitchbetweenthem)
• UnityGUIscriptingsystem
• Colliders
• Messages&events
Unityisapowerfultoolforgamedevelopment,suitableformanygamegenres,fromfirstpersonshooterstopuzzlegames.
• Lighting
• Particlesystems
• Blobshadows
• Scripting(AI,statemachines,playercontrols)
Thistutorialwillshowhowthesefeaturescanbeusedtogethertocreateagame.
WhatyoushouldalreadyknowThistutorialmakesextensiveuseofscriptingsoyoushouldbefamiliarwithatleastoneofthesupportedscriptinglanguages:JavaScript,C#orBoo.(JavaScriptisusedforthescriptsinthistutorial.)
ItisalsoassumedthatyouarefamiliarwithUnity’sinterfaceandknowhowtoperformbasicoperations,suchaspositioninganassetinascene,addingComponentstoaGameObject,andeditingpropertiesintheInspector.
ProjectOrganizationUnitydoesnotattempttoforceaparticularwayoforganizingyourproject'sassets.Youmaypreferorganizingyourassetsbyassettype,withseparatefoldersfor,say,"Textures","Models","Soundeffects"andsoon.AtUnityTechnologies,wehavefoundthisworkswellforsmallerprojects.Formorecomplexprojects,ourusersgenerallyrecommendorganizingassetsbyfunction,perhapsgroupingthemunderfolders suchas"Player","Enemies","Props","Scenery",andsoon.
Thistutorial’sprojectwasworkedonbyanumberofteammembersandgreworganicallytoreflecttheirdifferentconventionsandstyles.Intheinterestsofauthenticity,wehavedecidedtoleavetheproject'sorganizationasitwasasthisismorerepresentativeofa'smaller'project'sorganizationandstructure.
AbstractGameObjects&ComponentsUnity'sdesignplaceseachscene'sassetsatthecenterofthedevelopmentprocess.This makesforaveryvisualapproachtogamedevelopment,withmostoftheworkinvolvingdragginganddropping.Thisisidealforthebulkofleveldesignwork,butnotallassetscanbedisplayedinthisway.Someassetsareabstractratherthanvisualobjects,sotheyareeitherrepresentedbyabstracticonsandwireframegizmose.g.AudioSourcesandLightsorarenotdisplayedatallwithintheSceneView.Scriptsfallintothislattercategory.
ScriptsdefinehowassetsandGameObjectsinaUnitySceneinteractwitheachotherandthisinteractivityisatthecoreofallgames.Forthisreason,itisusuallyagoodplantokeepinformativenotesinsideyourscripts.
Thistutorialwillassumeyoucanreadtheprovidedscriptsandunderstandthemanycommentsliberallysprinkledthroughoutthem.However,whenaparticularscriptingtechniqueorconceptisimportant,wewillcoveritindetail.
6
Thescriptsaredocumentedthroughtheuseofextensivecommentsandhavealsobeendesignedtobeasselfexplanatoryaspossibleintheirdesign.Weencourageyoutoreadthroughthescriptsasweintroducetheminthistutorial,studyingtheirworkings.Feelfreetoexperiment!
FilesThemostuptodatefilesforthisprojectcanbedownloadedfrom:http://unity3d.com/support/resources/tutorials/3DPlatformProject.zip
The“Scenes”foldercontainsthefinalresult:amainmenuScene,agameoverSceneandaScenecontainingthecompletedgamelevel.
ThistutorialassumesyoualreadyknowbasicUnitycontrols,suchaspositioningobjectsinascene,sothefirststartingpointScenealreadyhasthebasicsceneryandsomepropsinplace.
TypographicalConventionsThisisalongtutorialcontainingalotofinformation.Tomakeiteasiertofollow,somesimpleconventionsareused:
Background&TangentsTextinboxeslikethesecontainsadditionalinformationthatmayhelpclarifythemaintext.
Scriptingcodewillappearasshownbelow:
// This is some script code.Function Update(){ DoSomething();}
NOTE Thescriptsincludedinthetutorialincludeplentyofcommentsandaredesignedtobeeasytofollow.Thesecommentsareusuallyomittedinthecodefragmentsinthetutorialtexttosavespace.
ActionsyouneedtoperformwithinUnityareshownlikethis:
Clickonthis;
Thenthis;
ThenclickPlay.
7
Scriptnames,assets,menuitemsorInspectorPropertiesareshowninboldfacetext.Conversely,amonospace fontisusedforscriptfunctionsandeventnames,suchas
theUpdate()functioninthescriptexampleabove.
UnityConventionsUnityisauniquedevelopmentsystem.Mostdeveloperswillbeusedtoworkinginacodeeditor,spending90%oftheirtimeeditingcodeandevenwritingcodetoloadupanduseassets.Unityisdifferent:Itisassetcentricratherthancodecentric,placingthefocusontheassetsinmuchthesamewayasa3Dmodelingapplication.Forthisreason,itisworthunderstandingthekeyconventionsandterminologyuniquetoUnitydevelopment:
ProjectsAgamebuiltinUnitywillconsistofaProject.Thiscontainsallyourproject’selements,suchasmodels,scripts,levels,menus,etc.Usually,asingleProjectfilewillcontainalltheelementsforyourgame.WhenyoustartUnity2,thefirstthingitdoesisopenaProjectfile.(Ifyouhaveonlyjustinstalledit,thiswillbetheProjectfilecontainingtheIslandDemo.)
ScenesEachProjectcontainsoneormoredocumentscalledScenes.AsingleScenewillcontainasinglegamelevel,butmajoruserinterfaceelements,suchasgamemenus,gameoversequencesormajorcutscenesmayalsoliveintheirownScenefiles.ComplexgamesmayevenuseentireScenesjustforinitializationpurposes.ThusalllevelsinagamewillmostlikelybeScenes,butnoteveryScenewillnecessarilybeagamelevel.
GameObjects,Components,Assets&PrefabsKeytounderstandingUnityistherelationshipbetweenaGameObjectandaComponent.
GameObjectsAGameObjectisthefundamentalbuildingblockinUnity.AGameObjectisacontainerfordifferentpiecesoffunctionalitycalledComponents.AGameObjectalmostalwayscontainsmorethanoneComponent.AllGameObjectscontainaTransformComponent,whichdefinesitspositionandorientation.
GameObjectHierarchiesTherealpoweroftheGameObjectisitsabilitytocontainotherGameObjects,actingmuchlikeafolderinOSX’sFinder.ThisallowshierarchicalorganizationofGameObjects,soacomplexmodeloracompletelightingrigcanbedefinedunderasingleparentGameObject.(Infact,mostmodelswillappearinUnityasahierarchyofGameObjectsbecausethisreflectshowtheyaredefinedinthemodelingpackage.)AGameObjectdefinedinsideanotherGameObjectisconsideredachildGameObject.
8
ComponentsComponentsarethebuildingblocksofGameObjects.Withoutthem,theGameObjectwon’tdoanythinginteresting.
AComponentmayrepresentvisibleentities,suchasmeshes,materials,terraindataoraparticlesystem.OtherComponenttypesaremoreabstract,suchasCamerasandLights,whichdonothaveaphysicalmodelrepresentingthem;instead,youwillseeaniconandsomewireframeguidelinesillustratingtheirkeysettings.
AComponentisalwaysattachedtoaGameObject;itcannotlivealone.MultipleComponentscanbeattachedtothesameGameObject.GameObjectscansupportmultipleComponentsofcertaintypes―aGameObjectcancontainanynumberofscripts,forexample.Butothers,suchasthoseusedtodefineparticlesystems,areexclusiveandcanonlyappearonceinanysingleGameObject.Forexample,ifyouwanttodefinemultipleparticlesystems,youwouldnormallyuseahierarchyofGameObjects,eachcontainingitsownsetofparticlesystemComponents.
AssetsAllyourimportedAssetsappearintheProjectPaneandtheycanbealmostanything:asimplematerialortexture,audiofiles,orevenacomplete,prefabricatedGameObject(knownasa“Prefab”).
Forexample,aplayercharacterPrefabcouldbedefinedasasingleAsset,containingthemodelanditsassociatedanimations.ItcouldalsocontainscriptComponents,audioclipsandanyotherComponentsitneedstofunction,soyoucouldsimplydragitintoaSceneandinstantlyhaveafullyoperationalavatar.
CustomIcons&GizmosYoucantellUnitytodisplaycustomiconsandothervisualinformationforyourAssetsifyouwish.Wewillseeanexampleofthisinthenextchapter.
Yourproject’sAssetsareshownintheProjectPane.WhenyoudroponeintoyourScene,itappearsintheHierarchyPane,whichdefinesthecontentoftheScene.(ASceneistheequivalentofthestageinatheater.Itcanbealevel,amenu,amultiplayergamelobbywhateveryouwish.)TheProjectPaneisretainedacrossallScenes inyourProject.
PrefabsAPrefabisanAssetwhichhasbeendefinedasatemplate.ItistoUnitywhatatemplatedocumentistoawordprocessingapplication.WhenyouplaceaPrefabintoyourScene,UnityplacesalinktothePrefabintotheHierarchyPane,notacompletecopy.Thisiscalledinstantiation.EachlinkyoumakeisreferredtoasaninstanceofthePrefab.
IfyouclickonaPrefabinyourProjectPaneandtweakitssettings,youwillfindthatthosechangesareinstantlyreflectedinalltheinstancesinyourScene.ThismakesPre
9
fabsidealformanyreusableelements,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,EthanVosburghwhoproducedthebeautifulassetsforthistutorial.
10
FirstSteps
AnimatingLerpzInthischapterwewilllookat:
• Implementingthirdpersonplayerandcameracontrols
• Controllingandblendinganimations
• Usingparticlesystemstoimplementthejetpack’sthrusters
• Addingablobshadowtotheplayer
• Maintainingtheplayer’sstate
• Handlingplayerhealth,deathandrebirth.
Beforewecanbegin,weneedtoknowwhatthisgameisallabout.Inshort,weneed...
ThePlotOurheroisLerpz:analienvisitingRobotWorldVersion2.ThisreplacedRobotWorldVersion1,whichsufferedaparticularlybrutalsegmentationfaultandabruptlycrashedintoitssunmanyyearsago.
Unfortunately,Lerpzhashadsomebadluck:hisspaceshiphasbeenimpoundedbythecorruptlocalpolice.Afterlookinghighandlow,Lerpzhasfoundhisspaceship,but
Everyplatformgamehasitsstarcharacterwhotheplayercontrols.OurstarisLerpz.
howcanhegetitbackfromMr.Big’snastier,obsessivecompulsivecousin,Mr.EvenBigger?
Mr.Biggerlovesnothingmorethanartisticallyarrangingfuelcanistersonhisfloatingpatio.Heparticularlyadmireshowtheyglowwhenheplacesthemonhoverpads.(And,ofcourse,they’recheaperthanfittingnormalgardenlights.)
Butthere’ssomethingMr.Biggerhasn’trealized!Thankstohispennypinchingways,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(thenichewiththeyellowchevrons),neartheJail.(Seethescreenshotonthenextpage.)
IfyouclickPlaynow,youshouldseeLerpzstandinginthecourtyardoutsidethejail.Atthisstage,Lerpzcannotbemovedandthecameraalsoneedstobelinkedtoourplayer'scharacter.
ClickthePlaybuttonagaintostopthegame.
WeneedtogetLerpzmoving,butfirst,weneedtostepbackamomentandtakealookatourcamera.
12
PositioningLerpzintheScene.
ThirdPersonCameras.Inafirstpersonshooter,thecameraistheplayer'spointofview,sothereisnoneedtoworryaboutmakingitfollowanotherobjectaroundthescene.Theplayercontrolsthecameraobjectdirectly.Firstpersoncamerasarethereforerelativelyeasytoimplement.
However,athirdpersonviewpointcamerarequiresacamerathatcanfollowtheplayeraround.Thisseemssimpleenoughuntilyourealizethecameraalsoneedstoavoidgettingscenerybetweentheplayer'scharacterandthecamera'sviewpoint.Thiscanbeachievedusingraycastingtocheckforunwantedobjectsbetweenthecameraandplayeravatar,buttherearesomespecialcasestoconsider.Forexample:
• WhathappensifLerpzisbackedupagainstasolidwall?Shouldthecameramoveaboveandlookdownontheplayer?Shoulditmovetotheside?
• Whatifanenemygetsbetweenthecameraandourplayeravatar?
• Howshouldtheplayercontrolswork?Shouldtheyberelativetothecamera'sview?Ifso,thiscouldgetveryconfusingifthecameramovesinanunexpecteddirectiontoavoidanobstacle.
Anumberofsolutionsforthirdpersoncamerashavebeentriedovertheyears.It'sarguablethatnonehaveeverbeen100%perfect.Somesolutionsfadeoutanythingbetweenthemandtheirfocus,makingwallsorenemiessemitransparent.Otheroptionsincludecameraswhichfollowtheplayeraround,butwhichwill,ifnecessary,movethroughwallsandbuildingstokeeptheplayer'sviewconsistent.
13
Theprojectsuppliedwiththistutorialincludesafewdifferentcamerascripts,butforthepurposesofthistutorial,we'lluseSpringFollowCamera.You'llfinditintheProjectPaneinsidetheCamerasubfolderoftheScriptsfolder.
DragtheSpringFollowCamerascriptfromtheProjectPaneontotheNearCameraobjectintheHierarchyPane.
ClickPlay.
Youwillgetanerrormessage.ThisappearsjusttotherightofthePlay,PauseandStepbuttonsatthebottomofUnity’swindow.
BringuptheDebugConsole(Shift+Cmd+C/Shift+Ctrl+ConPC),ifitisnotalreadyvisible.
Thisdisplaysanywarnings,errorsandotherdebugginginformationfromyourgame.Youwillprobablyseealotofcopiesoftheerrormessagerepeatedinthelog.Highlightoneandthepanebelowthelogwillshowabitmoreinformationaboutthiserrormessage,asshowninimage3.1.
“Notarget”errormessage.
14
TIP Wheneverpossible,theDebugLogwindowwillshowalinelinkingtotheoffendingGameObjectintheHierarchy,(ortotheProjectPaneifthefaultisinaPrefaborScript).Youcanseethislineinthescreenshotabove.
TheUnassignedReferenceExceptionerrortypeisoneyouwilllikelyseeveryfrequentlyifyou’renewtoUnity.Itsoundsscary,butallitmeansisthatascriptvariablehasnotbeenset.TheDebugLogexplainsthistoo,solet’sdoasitsuggests:
ClickontheNearCameraobjectintheHierarchyPaneandlookattheSpringFollowCamera(Script)Component’sproperties.
TheTargetpropertyissettoNone(Transform).Thisdefinesthetargetobjectwewantthecameratopointat,solet’ssetit:
Stopthegameifyouhaven’tdonesoalready.
Ifit’snotalreadyselected,clickontheNearCameraobjectintheHierarchyPane.
DragourPlayerGameObjectfromtheHierarchyPaneontotheTargetsettingtosetit.
MakingChangesWhilePlayingWhenyouareplayingthegame,Unitywillletyoutweakthepropertiesofthevarious gameobjectsandcomponentsinthegame.However,itwillnotsavethem!Themomentyoustopthegame,anychangeswillbediscarded!
Ifyouwantyourchangestostick,alwaysstopthegamefirst!
IfyouclickPlaynow,thecamerastillwon'twork.YouwillseeaerrorsrelatingtotheSpringFollowCamerascript.ItneedsthetargettohaveaThirdPersonControllerscriptattachedtoit.Thisisbecauseathirdpersoncameraiscloselytiedtotheplayercontrols:itneedstoknowwhattheplayerisdoingsothatitcanreactaccordingly.
Thefinalsettingsshouldlookasshownintheimagebelow:
15
SpringFollowCamerascriptsettings.
Experimentwiththenumbersifyoudon'tlikethewaythecameraworks;thisisasubjectivejudgementandthereisnosinglecorrectsettingforsomethinglikethis.
Thisisthefirstinaseriesofdependenciesthatweneedtodealwith.
CompletetheconnectionbetweenthecameraandtheplayerbydraggingtheThirdPersonControllerscriptfromtheScripts>PlayerfolderintheProjectPaneontoourPlayerGameObject(intheHierarchyPane).(ThiswillbreakthePrefabconnection.)
TheThirdPersonControllerscriptalsohasitsownrequirementsanddependencies.ThemostimportantoftheseistheCharacterControllercomponent.Luckily,thescriptalreadytellsUnityaboutthis,soUnitywilladdthiscomponentforus.
Connections&Dependencies.Unityexcelsatshowingvisualassets,butthesealsohavetobeconnectedtoeachothertoprovidetheinteractivityweexpectfromagame.Theseconnectionsaredifficulttoshowvisually.
Theseconnectionsareknownasdependencies,andit'swhatyougetwhenoneobjectrequiresasecondobjecttofunction.Thatsecondobjectmay,inturn,requireyetmore
16
objectstowork.Theresultisthatyourassetsaretiedtoeachotherwithmyriadvirtualbitsofstringscriptstyingthemalltogethertomakeagame.
Definingallthesedependenciesisakeyelementofgamedesign.
WenowneedtoaddatagtoourPlayerGameObject.ThisissothatscriptscanfindourPlayerintheScenebysimplytellingUnitytofindtheGameObjectwithsaidtag.
WithourPlayerobjectdisplayedintheInspector,opentheTagdropdownmenuandchoosethe“Player”tag,asshownbelow.
SelectingthePlayertag.
NOTE TheTagslistedinthemenushownaboveareprovidedbyUnitybydefault.We’lllearnhowtocreateourownTagsandLayerslater.
TheTagwillbeusedlater,solet’sreturntotheCharacterControllerandourscript.
SelectthePlayerobjectandlookintheInspector.Itshouldlooksimilartothis:
17
CharacterControllerandThirdPersonControllerScriptComponentsinplace.
OurnextstepistoadjusttheCharacterController.Atthemoment,theCapsuleCollideritusesislocatedtoofardownintheYaxis,soLerpzstandsonthinair.(YoucanseetheCollider'spositionintheSceneView:it'sthelongbluecylindricalwireframeshape.)WeneedtochangetheCenterYvalue.
AdjustingtheCharacterController’sCapsuleColliderdisplayedasabluewireframe.
18
PositiontheCapsuleColliderasshowninthescreenshotabove.(AlittleexperimentationsuggestssettingtheCharacterController’sCenterYto1.03willalignitslowerendperfectlywithLerpz'sfeet.)
IfyouclickPlaynow,Lerpzshouldnowmovearoundwhenyouusethecontrolkeyswithhisfeetfirmlyontheground.
TheCharacterController&theThirdPersonControllerscriptInmostgames,theplayer’savatariscapableofimpossiblephysicalfeatssuchasturningandstoppinginstantaneously,leapingimprobabledistancesandotheractionswhichwouldbedifficulttomodelusingtraditionalphysics.TheCharacterControllerthereforedecouplesourplayeravatarfromthephysicsengine,providingbasicmovementcontrols.
TheCharacterControllersimplifiesmovementforaplayer(andmanynonplayer)charactertypes.Itconsistsofacapsulecollidertiedtoabasicmovementsystem,allowingourcharactertomovearound,climbstepsandslideupordownslopes.YoucanchangethemaximumstepandslopesizesintheInspector.
TheCharacterControllerisnormallyusedwithascript.ThistalkstotheCharacterControllerandextendsitscapabilitiestomeettheneedsofthegame.Inourproject,theThirdPersonControllerscriptperformsthisfunctionandaddsthenecessarysupportforourplatformgame.Itreadsthejoystick,keyboard,mouseorotherinputdeviceandactsuponittocontroltheplayer’savatar.
TheUnityInputManager(Edit>ProjectSettings>InputManager)allowsyoutodefinehowtheinputdevicescontroltheplayer.
NOTE Thereisnothingspecialaboutthescriptsweareusingfortheplayer.TheyareperfectlyordinaryUnityscriptswhichhavebeenbuiltforthisproject.ThereisnodefaultCharacterControllerscript.
TheThirdPersonControllerscriptisalreadypartofthePrefab,sothereisnoneedtoaddit.
ThenextstepistomakeLerpzanimatecorrectlyandaddtheadditionalmovements,suchasjumpingandpunching...
AnimatingLerpzAtthispoint,Lerpzisjustglidingacrossthescenery.ThisisbecausetheCharacterControllerdoesn'thandleanimation.Itdoesn'tknowanythingaboutourplayer'smodelorwhichanimationsequencesapplytoeachmovement.WeneedtoconnectLerpztohisanimationsequencesandthisisdonewiththeThirdPersonPlayerAnimationscript.
UsetheComponentmenutoaddtheThirdPersonPlayerAnimationscripttothePlayergameobject.
IfyouclickPlaynow,you'llseeLerpzanimatingcorrectly.
19
Sowhat’sgoingonhere?Whatdoesthisscriptdo?TheanswerliesinhowUnityhandlescharacteranimationdata.
CharacterAnimationCharacteranimationsequencesarecreatedwithinamodelingpackage,like3DStudioMax,Maya,BlenderorCheetah3D,.OnimportingintoUnity,thesesequencesareautomaticallyextractedandstoredinanAnimationcomponent.
Theseanimationsequencesaredefinedonavirtualskeleton,whichisusedtoanimatethebasicmodel.Theseskeletonsdefinehowthemodel'smeshtheimportantdatadefiningthevisiblesurfacesofthemodelitselfismodifiedandtransformedbytheenginetoproducetherequiredanimation.
Skeletons&ArmaturesIfyouarefamiliarwithstopmotionor“claymation”animationtechniques,youmaybeawareoftheiruseofmetalarmatures.Theanimatedmodelsarebuiltaroundthesearmatures.Thevirtualskeletonsusedin3Dmodelsaredirectlyequivalenttotheseandarerarelyascomplexasrealskeletons.
Themeshcomponentofsuchmodelsiscommonlyreferredtoasaskinnedmesh.Thevirtualskeletonprovidesthebonesbeneaththemeshanddefinehowitanimates.
AnimationblendingCharacteranimationsareusuallyblendedtogethertoprovidethenecessaryflexibilityforagame.Forinstance,ananimatedwalkcyclecouldbeblendedwithaseriesofspeechanimations,theresultbeingacharacterthatiswalkingandtalkingatthesametime.
Blendingisalsousedtoproducesmoothtransitionsbetweenanimations,suchasthetransitionbetweenawalkcycleandapunchsequence.
WeneedtouseascripttotellUnitywhenweneedtoswitchanimations,whenanimationblendingisneededandhowitshouldbedone.Thisiswherescriptingcomesin.
TheThirdPersonPlayerAnimationscriptTheLerpzmodelwe'reusingwascreatedformultipleprojectsandcontainsfifteenanimationsequences.Onlyelevenareusedinthistutorial.IfyouselectthePlayerobjectintheHierarchypaneandlookattheInspector,youwillseeallfifteenanimationsequenceslistedwithintheAnimationcomponent,ofwhichonlythefollowingareactuallyusedinthistutorial:
• WalkThenormalwalkcycle.
20
• RunArunninganimation.(HoldtheShiftkeywhileplayingtorun.)
• PunchPlayedwhenattackinganenemyrobotguard.
• JumpPlayedwhenLerpzleapsintotheair.
• JumpfallPlayedwhenLerpz'sleapreachesitsapexandhestartstofall.
• IdleAloopplayedwhenLerpzisidle.
• WalljumpAbackflipanimationplayedwhenLerpzjumpsoffawall.
• JetpackJumpPlayedwhenLerpz'sjetpackisslowinghisfall.
• LedgefallPlayedwhenLerpzstepsofftheedgeofaplatform.
• ButtstompPlayedwhenLerpzhasbeenstruckbyarobotguard.
• JumplandPlayedwhenLerpzlandsafterajumporfall.
ThemodelandanimationsforLerpzwerecreatedusingMayaandimportedintoUnity.Formoreinformationaboutimportingmeshesandanimations,pleaserefertotheUnityManual.
MostoftheseanimationsaredealtwithbytheThirdPersonPlayerAnimationscript,whichchecksthecontrolstheplayerisusingandreactsaccordingly.Someanimationsarelayeredoverotherswhileothersaresimplyqueueduponeafteranother.Thescriptismostlyasetofmessageresponderfunctions.TherelevantmessagesarefiredoffbytheThirdPersonControllerscript,whichreadstheinputdevicesandupdatesthecharacter’sstateaccordingly.
Lerpz'sattackingmovehispunchisdealtwithinaseparatescript,ThirdPersonCharacterAttack.(We’lladdthisscriptlater.)Thismayseemanarbitrarysplit,butitisnot:mostofthebasicmovementswalking,running,jumping,etc.areprettysimilarnomatterwhatyourplayer'scharacterlookslike.However,attackinganddefensivemovestendtobemuchmorevaried.Insomeplatformgames,theplayercharactermighthaveagun;inanother,hemightperformamartialartsmove.
Inthistutorial,LerpzisamasterofthepopularWesternmartialartknownasFisticuffs,anamewhichtranslatesto"hittingyouropponentveryhardwithyourfist".Theanimationisasimplepunchinganimation.
GizmosThirdPersonCharacterAttackalsoincludesausefultestingfeature:agizmowhichdrawsaspheretorepresenttheareaaffectedbyLerpz'spunchingaction.GizmosaredrawninsideoneoftwoGizmodrawingmessagehandlingfunctions.Inthisexample,thegizmowireframeyellowspheredrawnatthepunchpositionanddisplayingitsareaofeffectisdrawninresponsetotheOnDrawGizmosSelected()function.
21
ThisfunctionmustbestaticandwillbecalledbytheUnityEditoritself.AnalternativeisOnDrawGizmos(),whichiscalledbytheUnityEditoreveryupdatecycle,regardlessofwhethertheparentGameObjecthasbeenselected.
TheJetPack
Lerpz’sJetpackinaction.
Atthispoint,ourcharacterisrunningandjumpingaround,buthisjetpackisnotyetworking.Lerpzusesthejetpacktoslowhisrateofdescent.Themovementisalreadyinplace,butthejetpack'sjetsdon'tanimateatall.Tomakethejetswork,wewillneedtoaddtwoParticleSystemsandaPointLightcomponent.Theparticlesystemswillproduceaflamelikeeffect,whilethepointlightwillgivetheillusionoftheflamesactingasasourceofillumination.
TIP Ideallywewouldhaveapointlightsourceforeachjet,butthejetexhaustsarecloseenoughtoeachotherthatwecangetawaywithjusttheone.Aslightsarecomputationallyexpensive,thisisahandyoptimization.
WhatisaParticleSystem?ParticleSystemsemitdozensofparticlesusuallyflat2Dbillboardsorspritesintothe3Dworld.Eachparticleisemittedatasetspeedandvelocity,andlivesforacertaintime.Dependingonthesettingsandbillboardmaterialsused,theseparticlesystemscanbeusedtosimulateanythingfromfire,smokeandexplosionstostarfields.
22
AddingtheParticleSystemsUsetheGameObjectMenutocreateanemptyGameObjectintheHierarchyPane.
RenamethisGameObject"Jet".
WiththenewGameObjectselected,add:
AnEllipsoidParticleEmitter
AParticleAnimator
AWorldParticleCollider
AParticleRenderer
Uncheckthe“Enabled”checkboxintheInspectorfortheParticleRendererComponent.Thiswilldisableittemporarily.
PositiontheJetdirectlybelowLerpz'srighthandjetexhaust.
ReenabletheParticleRenderer.
AdjustthesettingsfortheEllipsoidParticleEmitterasshownbelow:
EllipsoidParticleEmittersettings.
23
Thesesettingsresultinanarrowstreamofparticleswhichwewillusetosimulateajetofflame.
TIP Iftheparticlesarenotmovingdirectlydownwards,useUnity'srotationtoolstorotateourobjectuntilthejetismovinginlinewiththejetpack'sexhaust.
Whenwe'redone,theparticlesystemwillbeattachedtothePlayer's"torso"childobjectinitshierarchy.Thiswillcausethejettofollowtheplayer'smovements.Atthispointhowever,we'reprimarilyinterestedingettingittolookright,sodon'tworrytoomuchaboutaccurateplacement.
TheMinSizeandMaxSizesettingsdefinethesizerangeoftheparticles.TheMinEnergyandMaxEnergysettingsdefinetheminimumandmaximumlifespanoftheparticles.Ourparticleswillliveforonlyashorttime0.2secondsinthiscasebeforefadingaway.
Wesetthequantityofparticlestoemitto50.TheMinEmissionandMaxEmissionrangedefineshowmanyparticleswewantonscreenatanyonetime,andhowvariablethisemissionshouldbe.Wehavechosentosettheminimumandmaximumrangetosimilarvaluesorit'lllooklikethejetissputteringratherthanblastingawaysmoothly.Theresultshouldbeasmoothflowofparticles.
NOTE We'vedisabled"SimulateinWorldspace"here.Thishelpsgivetheimpressionthatwehaveahot,fastjetofgasratherthanamuchslowerflame,eventhoughtheparticlesaren'tmovingallthatquickly.BymakingthejetignoreLerpz'stwistsandturns,itlookssimilartothehot,steadyflamefromablowtorch.
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“Opacity”slider.Thetableshownwiththecolorsettingsalsoincludethisas,byreducingopacitythroughtheanimationcycle,theparticle'flames'willappeartofadeawayastheycool.
NOTE TheOpacitysettingmodifiestheAlphachanneloftheparticle,defininghowtransparentitis.Theparticlematerialweusealreadyincludesalphachannelinformation,whichisthenmodifiedfurtherbytheOpacitysetting.
NextistheParticleRenderer.ThisComponentdrawseachparticle,soitneedstobetoldhowtheparticleswillappear.Italsodefinesthematerialtousetorendereachparticle.Wewantaflamelikejeteffect,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,buttheresultingflamelikeeffectdoesnotemitlight.Tocompletetheillusion,wewillcreateaseparatePointLightGameObject.We’llswitchitonandoffatthesametimeasthejets.Theresultwillbeajetofflamewhichappearstolightupitsimmediatesurroundings.(Wewillonlyuseasinglelight,ratherthanonelightperjet;thissavesprocessingpowerwhilemaintainingourillusion.)
CreateanewPointLightGameObject.
26
Namethis"JetLight"andpositionitbetweenthetwomodeledjetsonLerpz'sjetpack.(We’llcomebacktoourJetparticlesystemshortly.)Thislightwillcreateanillusionthatthejetsareemittinglight.
Forthiseffecttowork,weneedabrightpointlightwithahighintensity.
SelecttheLightandadjustsettingsasshown:
JetpackLightsettings.
Whynoshadows?Shadowsarecomputationallyexpensiveformosthardware.Itmakessensetoavoidcalculatingthemifwecanavoidit,andthisisoneareawherewecangetawaywithit.Thejetsaren'tverybig,sotheyonlyneedtolightupLerpz'sback.Thepointlightwillalsobereflectedinnearbyscenery,butitwon'tbebrightenoughtomakethelackofshadowsnoticeable.
ThenextstepistoupdatethePlayerGameObjecttoincludeourjetandlightobjects.Todothis,we’llfirstaddourJettotheProjectPaneasaPrefab:
IntheProjectPane,selectthe(empty)Playerfolder,thenclickCreate...
Fromthedropdownmenu,choosePrefab.ThiswillcreateanemptyPrefabobjectinside.
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.ThiswillcreatetwoJetinstances.
RenamethetwoJetinstancesJetLandJetR,
DroptheJetLightontothesametorsoobject.
Youshouldnowhaveanobjecthierarchythatlookssomethinglikethis:
JetpackHierarchy.
UseUnity'smanipulationtoolstopositioneachJetPrefabunderitsrespectivejetoutletinLerpz'smodel.Youmayneedtorotatethem,sothattheparticlesaregoingintherightdirection.
MovetheJetLighttoapointbetweenthetwoJetPrefabs.
Whenyou'veachievedthis,Lerpzshouldnowhavetwoflamingjetsgushingfromhisjetpackashemovesaround.We'realmostdone!
ThefinalstepistomakeJetPrefabsandJetLightobjectsactivateonlywhenhe'sjumping.Thisisachievedthroughscripting.
LookfortheJetPackParticleControllerscriptinScripts>Playeranddragthisontothetopmost"Player"objectintheHierarchyPane.Thisaddsthescripttoourplayercharacter.
28
Youshouldnowfindthatthejetpackworksasexpected.Thescriptcontrolsthetwoparticlesystemsandthelight,synchronizingthemwithLerpz’smovementsandtriggeringallthreeelementstogetherwhenevertheplayerpressesthejumpbuttontojumportoslowhisdescent.
Lerpz’sJetpackinaction.
BlobShadowsLerpzmustbeeasytoidentifyatalltimes,sothatplayerswon’tlosetrackoftheiravatarswhenthegamegetsvisuallybusy.Mostofthisworkisuptotheartistsandthegame’sdesigner,buttherearesomeelementswhichhavetobehandledbyUnityitself.
Oneofthemostimportantoftheseisshadowingandlighting.Toaidperformance,theeffectsoflightingareoftenprerenderedintothetexturesbytheartistusingamodelingpackageatechniqueknownas“baking”.Thistechniqueonlyworkswellonstaticobjects,suchassetsandfixedprops.(Wehavedeliberatelyavoidedthistechniqueforthistutorial’sassets.)Acharacterwalkingunderastreetlightneedstoreacttothatlightinrealtime.Theroadbeneaththecharactercanhavethelightingbakedin,butthecharactercannotusethistrickandalsoneedstoreacttothelights.
Thesolutionistopositiondynamiclightswheretheyneedtobeifyouuse”baked”textures,remembertoaddalightwhereveroneisimpliedbythebakedlightingbutmakethelightsonlyaffectmovingobjects.Lightshavealreadybeenplacedinthesceneforyou.
Thisleavesonefinalelement:shadows.
29
Ina3Dplatformgame,theshadowplaysakeyroleintellinguswherethecharacterwilllandifheisjumpingorfalling.ThismeansLerpzshouldhaveagood,visibleshadow,whichisnotthecaseatthemoment.
Shadowscanbeproducedusinglights,withtheshadowcomputedandrenderedinrealtimebythegraphicsengine.However,suchshadowsareexpensiveintermsofprocessingpower.Inaddition,notallgraphicscardscancomputeshadowsquicklyoreffectively;oldercardsmaynotbeabletodosoatall.
Forthisreason,wewilluseaBlobShadowforLerpz.
AddingaBlobShadowABlobShadowisacheat.Insteadofcastingraysoflightandcheckingiftheyhitanything,wesimplyprojectadarkimageinthiscasejustacircularblackblobontoanythingbelowourcharacter.Thisisquickerandeasierforthegraphicscardtodo,soitshouldworkwellonallrangesofhardware.
UnityincludesaBlobShadowprefabinitsStandardAssetscollection,soweshallusethisratherthancreatingourown.ThisassethasalreadybeenimportedandaddedtotheprojectintheBlobShadowfolder.OpenthisfolderandclickontheblobshadowprojectorPrefabanddragitontoourtoplevelcharacterobjectPlayerintheHierarchyPane.ThisshouldaddtheProjectorjustbelowthetoplevelinourPlayerobject'shierarchy:
TheBlobShadowProjectorPrefabinthePlayer’shierarchy.
Next,youwillneedtomodifytheblobshadowprojector’sPositionandRotationdatasothatitisdirectlyaboveourcharacterandpointingdirectlydownattheground.
Selectthe4Splitlayout.
30
Settheblobshadowprojector’sRotationvaluesto90,180and0respectively.
NowusethesideandtopviewstomovetheprojectordirectlyoverLerpz’head.Youmightwanttomoveitupordownalittleuntilyou’rehappywiththeshadow’ssize.
CreatinganewLayerAtthispointyouwillhavenoticedthattheblobisalsobeingprojectedontoLerpz.Wedon’twantthistohappen.Therearetwooptionstogetaroundthis:movetheNearClipPlanesettingfurtherawayfromtheprojector,orsimplytellitnottoprojectontoobjectsinspecificLayers.Weshallusethelatteroption.
WhynotadjusttheNearClipPlane?Thistechniquemightseemeasiestatfirstglance,buttheplanewouldneedtobeadjustedbyscriptingtotakeintoaccountLerpz’sanimations.Hisfeetmovefurtheroutwhenhejumps,thenbrieflymovealittlecloserwhenhelandsagain.SincetheshadowmustalwaysbeprojectedontothegroundonwhichLerpzstands,thismeanstheNearClipPlanecannotremainthesamethroughoutthesesequences.
OpenupthePlayerGameObject.
OpentheLayerdropdownintheInspector.
ChooseAddnewlayer…
ClickonthefirstemptyUserLayerentryandnameitnoShadow.
YoushouldnowseesomethinglikethisinyourInspector:
AddinganewLayerusingtheTagManager.
31
NowclickbackonthePlayerobjectintheHierarchyPanetobringuptheusualInspectorsettings.
Clickonthe“Layer”dropdownandsetittothenewlayername,noShadow.UnityasksifyouwishtoapplythistoallchildGameObjects:click“ChangeChildrenLayers”.
NextweneedtotelltheBlobShadowProjectornottoprojectontoobjectsinthisLayer.
Bringuptheblobshadow’spropertiesintheInspectorandlookattheIgnoreLayersentryintheProjectorComponent.
UsethedropdownmenutotherighttoselectthenoShadowLayer(whichshouldapplytochildrenaswell),asshown:
TheBlobShadowProjectorsettings.
Ifyounowplaythegameandmovearoundyoushouldseetheshadowbehavingprettymuchasexpected....exceptifyoujumparoundnearthecollectablefuelcells.Ifyoutrythis,youwillseetheitemshowingtheblobshadowtoo.
Wewantcollectableitemstostandoutatalltimes,itmakessensetotelltheBlobShadowProjectortoavoidthesetoo.
We’llbelookingatthesecollectablesinmuchmoredetailinthenextchapter,butlet’sfixthisproblemnowwhilewe’rehere.
First,stopthegame.
NowgototheProjectPaneandlocatetheFuelCellPrefabandHealthLifePickUpPrefabobjects.You’llfindtheminsidethePropsfolder.
SelecttherootobjectofeachPrefabandsetitsLayertonoShadow,asshownbelow:
32
Layerchangedto“noShadow”
NOTE Whenmakingachangetoaparentobject,Unitywilloftenaskifthechangeshouldalsobeappliedtothatobject'schildren.Doingsocanbedangerousifyouhaven'tthoughtthroughalltheramifications.
Inthiscase,wewantallthechildobjectsofthe"FuelCellPrefab”and“HealthLifePickUpPrefab”GameObjectstobeinthesame"noShadow"layer,sowhenUnityasks,agreetopropagatethechanges.
ScriptingConceptsHistoryislitteredwithsurprisinglycomplexmachinesknownasautomatabuiltbyourancestorsforthepurposesofentertainment.Somewereveryelaborateandcouldevenperformsimpleplaysusingpuppets.Otherswereinteractiveandchangedtheirbehavioraccordingtouserinput.Thesemachineswerefundamentallythesame:thedesignercreatedassetspuppets,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,howmuchdamageLerpzcantakeorthatLerpzhasajetpack.HowcanUnitybeawareoftherobotguards'requiredbehaviororhowtheyshouldinteractwithLerpz?
Thisiswherescriptscomein.Weusescriptstoaddtheinteractionandstatemanagementspecifictoourgame.
Ourgamewillneedtokeeptrackofanumberofstates.Theseinclude:
• Theplayer’shealth;
33
• Thenumberoffuelcanisterstheplayerhascollected;
• Whethertheplayerhascollectedenoughfueltounlocktheforcefield;
• Whethertheplayerhassteppedonajumppad;
• Whethertheplayerhastouchedacollectableitem;
• Whethertheplayerhastouchedthespaceship;
• Whethertheplayerhastouchedarespawnpoint;
• WhethertheGameOverorGameStartscreensshouldbeshown;
• ...andmore.
Manyofthesestatesrequireteststobemadeagainstotherobjects’statestoensurethey’reuptodate.Sometimesweevenneedintermediatestates,toaidatransition.Forexample,collectingafuelcanisterwillforceachecktobemadetoseeiftheplayerhasenoughtoshutdowntheforcefield.
Organization&StructureInthistutorialthestatemachinesfortheplayer,thelevelandtheenemiesarehandledbyabunchofscriptslinkedtovariousGameObjects.Thesescriptstalktoeachother,sendingeachothermessagesandcallingeachother'sfunctions.
Thereareanumberofwayswecansetuptheselinks:
• ByaddingalinkexposedintheInspector,ontowhichyoudroptherelevantobject.Thisisidealforgeneralpurposescriptswhichyouintendtoreuseinotherprojects.
Thisisthemostefficientasthescriptmerelyplucksthedatafromtherelevantvariableanddoesn'tneedtodoanysearching.However,itdoesassumeyouknowinadvanceexactlywhichobjectorcomponentyou'llbelinkingto.
WeusethisoptionforthecutscenecamerasinLevelStatus.(Thisscript,currentlyjustashortstub,isalreadyattachedtotheLevelGameObject.)Thisgivesustheflexibilitytosetupmultiplecameras,oneforthe"levelexitunlocked"cutsceneandanotherforthe"levelcomplete"sequence.Inpractice,we'reonlyusingtwocamerasinthegame;onefortheplayerandthe"levelcomplete"sequence,theotherforthe"unlocked"cutscene.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,cacheslinkstoanumberofotherobjects,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,sothedesignusedforthistutorialusessomeobjectorientationtechniquestoalleviatethis.
Thescriptwhichdealswithaparticularpartofthestatemachinee.g.playeranimationshouldalsobetheonewhichkeepstrackoftherelevantstatevariables.Thiscanmakethingsalittlecomplicatedwhenascriptneedstoaccessastatevariablestoredinanotherscript,whichiswhysomescriptscachesomevalueslocallytomakeaccesstotheinformationquicker.Thistechniquealsooccasionallyresultsinchainsofcommands,whereafunctioninonescriptmerelycallsasimilarfunctioninanotherscript.Thehandlingoftheplayer'sdeathandhealthisanexampleofthis.
NOTE AsyougetmoreexperiencewithUnity,youwillfindotherwaystohandlestatesthatmaybebettersuitedtoyourowngames.Thedesignpatternusedinthistutorialshouldnotbeconsidereda“onesizefitsall”solution!
35
Ifyouwish,youcanturnourPlayerGameObjectintoaPrefabcontainingallourchanges,soyoucanreuseitinotherprojectsasastartingpoint:
ClickonthePlayerfolderintheProjectPane.
CreateanewPrefab.(It’llappearinsidethePlayerfolder.)
GivethenewPrefabanappropriatenameIsuggestLerpzPrefab.
DragourPlayerGameObjectontoLerpzPrefabtocompetetheprocess.
Death&RebirthPlatformgamecharacterstendtoleadriskylivesandLerpzisnoexception.Weneedtoensurehelosesalifeifhefallsoffthelevel.Wealsoneedtomakehimreappearatasafespotontheleveloftencalleda“respawnpoint”sohecancontinuehisquest.
AnotherpointisthatifLerpzcanfalloffthelandscape,itisalsopossiblethatthelevel'sotherresidentscoulddosotoo,sothesemustalsobedealtwithappropriately.
Thebestsolutionforthisistouseaboxcollidertocatchanythingfallingoffthelevel.We'llmakeitverylongandbroad,sothatifaplayershouldtryusingthejetpackwhilejumpingoff,we'llstillcatchhim.However,Lerpzwillneedsomewheretorespawn.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()functioninLerpz’sThirdPersonStatusscript.
IfanotherobjectwithacolliderobjecthitsourGameObject,wesimplydestroyit,removingitfromtheScene,otherwiseit’llfallforever.
Inaddition,wehave:
• Theutilityfunction Reset()whichensuresanyrequiredComponentsarealso
present.ThisfunctioniscalledbyUnityautomaticallywhenaddingthecomponentfor
37
thefirsttime.ItcanalsobecalledintheEditorbyclickingonthecogwheelicontotherightofanyComponent’snameintheInspector:
TheResetmenucommand.
• The@Scriptdirective,whichalsoaddsthescriptdirectlytoUnity’sComponentmenu.ThisisaconvenienceandsaveshavingtohuntforitinsidetheProjectPaneusefulifyouhaveacomplexprojectwithlotsofassets.
Ifwetrytoplaythegameatthispoint,Unitywillcomplainbecauseitdoesn’tknowwheretomakeLerpzreappear.Thisiswhererespawnpointscomein...
RespawnPointsWhentheplayerdies,weneedsomewheresafeforhimtoreappear.Inthistutorial,Lerpzwillreappearatoneofthreerespawnpoints.WhenLerpztouchesoneofthesepoints,itwillbecomeactiveandthiswillbewherehereappearsifhedies.
LerpzstandingonanactiveRespawnpoint.
TherespawnpointsareinstancesoftheRespawnPrefabprefabobject.(You'llfinditintheProjectPane'sPropsfolder.)
Thisprefabisamodelofateleportbase,coupledwiththreecompleteparticlesystems,aspotlightandsomeotheroddsandends.Here'sthebasicstructure:
38
• RSBasecontainsthemodelitself:ashortcylindricalbasewithaglowingbluediscinthecenter.
• RSSpotlightisaspotlightobjectwhichshinesasubtlebluelightupfromthesurfaceofthemodel,givingtheillusionthatthebluetextureisglowing.
• Theremaininggameobjectsareparticlesystems.TheRespawnscriptattachedtotheparentRespawnPrefabobjectswitchesbetweentheseparticlesystemsdependingontheprefab'sstate:
‣ Iftherespawnpointisinactive,asmall,subtleparticleeffectisshownlookinglikeabrightbluemist.ThisiscontainedinRsParticlesInactive.
‣ Iftherespawnpointisactive,alarger,moreostentatiouseffectisshown.ThisiscontainedinRsParticlesActive.
Onlyonerespawnpointcanbeactiveonthelevelatanyonetime.Whentheplayertouchestherespawnpoint,acolliderobject(setasatrigger)detectsthisandtriggersactivationoftherespawnpoint.
‣ TheremainingthreeparticlesystemsRSParticlesRepawn1,RSParticlesRepawn2andRSParticlesRepawn3areenabledtogetherwhentheplayerisrespawnedattherespawnpoint.Theseareoneshotparticlesystems.Thescriptletstheseplay,thenrestorestheRsParticlesActiveparticlesystemoncethisoneshotsequenceiscompleted.
Theprefabcontainsascript,Respawn,whichcontrolsthestateoftherespawnpoint.However,inorderforthegametoknowwhichspecificrespawnpointtheplayerneedstobereturnedtowhenhedies,weneedtoarrangetherespawnpointsinahierarchyunderamastercontrollerscript.Let’sdothisnow:
DragtheRespawnPrefabintotheSceneView.
Positionitasshownintheimageonthenextpage.
RenamethisinstanceRespawn1.
Repeattheabovestepstwicemore.Youcanplacethesewhereveryoulikeaddmoreifyouwish!Isuggestputtingonenearthearenaatthefarendofthelevel,andanothernearthetreesinthegardenabovetheplatforms.
ThenextstepistocreateacontainerGameObject.
RenamethisRespawnPoints
MakealltherespawnprefabinstanceschildrenofRespawnPoints.
39
Positioningthefirstrespawnpoint.(Lerpzhasbeenmovedoutoftheshotforclarity.)
HowitworksWhentheSceneisloaded,UnitycallstheStart()functionineachinstanceoftheRespawnscript,wheresomeusefulvariablesareinitializedandpointerstootherelementsarecached.
Thekeymechanismiscenteredaroundthisstaticvariable:
static var currentRespawn : Respawn;
ThisdefinesaglobalvariablenamedcurrentRespawn.
Thestatickeywordmeansitissharedacrossallinstancesofthescript.Thisletsus
keeptrackofwhichrespawnpointisthecurrent,activeone.However,whentheScenebegins,noneofthepointsisactivated,soweneedtosetadefaultoneforourScene.TheUnityInspectorwillnotdisplaystaticvariabletypesatall,sothescriptdefinesanInitialRespawnproperty,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.Theseareplayedasoneshotsamples,exceptforanAudioSourceattachedtoeachRespawnPrefab.ThisComponentcontainsthe"active"sound,whichisaloop.Thescriptsimplyenablesordisablesthissoundasappropriate,eitherwhileplayingaoneshoteffectsuchaswhentheplayerisactuallyrespawningoractivatingtherespawnpointitselforwhentherespawnhasbeendeactivated.
NOTE Unitymakesitalmosttooeasytoaddsoundeffects.Wheneveryouplantoaddsuchanasset,considercarefullyhowitwillbeused.Forexample,wehaven'tincludeda"respawndeactivated"soundbecauseyou'dneverhearthesoundbeingplayed;you’reunlikelytopositiontworespawnpointswithinearshotofeachother.Ifyouweretoconverttheprojectintoamultiplayergame,youmightwanttoaddsuchasoundandthenecessaryscriptcodetohandleit.
Thescriptisnotcomplexandyoushouldfindthescriptcodeeasyenoughtofollow.Wewillreturntotherespawnpointsinlaterchapters.
41
SettingtheScene
FirstStepsInthissectionwewilllookatbuildingthegameworldwheretheactiontakesplace.Inmovieterminology,thismeansbuildingtheset,placingthepropsandwritingthescriptingthatletsourherointeractwiththem.
Ourfirststepistopreparethestage.Thetutorialfilealreadyhasthebasiclevelmeshsetupandpopulatedwithanumberofcollectableitems.Wewillplaceafewmorepropsandelements,butmosthavebeendoneforyouasplacingallofthesepropswouldmakeforaverylong,veryboringtutorial.
LightingThelevelisalreadyprovidedwithanambientlightaswellasmyriadpointlights.Theselightupthescenery,theplayer,theenemiesand,toalimitedextent,thepickups.
Inthisproject’scase,thelightswereplacedbytheartistwhomodeledthelevel.Lightingplayssuchanimportantroleincreatingtheambienceandsenseofplacethatitisusuallybesttoleavethistothemodelerwhobuiltthelevel.
42
Withourheronowmobile,thenextstepistogivehimsomethingtodo...
PlacingPropsThebasicsetisprovidedforyou,alongwithanumberofpropsalreadyinplace.
BuildingYourOwnLevelsThetutoriallevelwasbuiltbyarrangingscenerycomponentsinMayaandthenimportingthelevelintoUnity.Ifyouwouldliketoexperiment,orevencreateadditionallevels,theindividualsceneryelementscanbefoundintheBuildYourOwn!folderintheProjectPane.
Therearealargenumberofthefuelcellpropsandplacingallofthesewouldmakeforaverydulltutorial.Ifyouhavereadtheprevioustutorials,youwillalreadyknowhowtodothisanyway,sowewilllimittheplacementtothe"Health"pickups,thejumppadsandrespawnpoints,amongothers.
HealthPickupsWebeginwithasimpletask:addingsomecollectablehealthpickups.Thesearespinning,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’sUVcoordinatestogiveittheeffectofaripplingforcefield.
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.WeuseashortformulamultiplytheScrollSpeedvaluebythecurrenttimetodefineatextureoffset.
PrettyPropertiesWhenUnity’sInspectordisplayspropertiesandvariables,theirnamesareadjustedtomakethemlooknicer.Usually,thisjustmeanslookingforthecapitallettersinthenameandinsertingaspacebeforeeachone.Inaddition,Unitycapitalizesthefirstletterofthename.ThusscrollSpeedisdisplayedasScrollSpeed.
Whenatextureisrendered,thetextureitselfisusuallyjustanimage.ThemainTextureOffsetpropertyofamaterialtellsUnitythatitistodrawthetexture’simageoffsetwithinitsUVspace.Thiscanbeusedtoproducesomeveryeffectiveresultswithoutresortingtocomplexanimationsequences.
ExpandthelevelGeometryGameObjecttorevealthedifferentelementsoftheleveldata.WeneedtoanimatethefenceontheimpoundFenceobject,sodropthefenceTextureOffsetscriptontothis.
ScriptingtheCollectableItemsAtthemomentLerpzdoesnotpickupanyoftheitemsonthelevel.ThisisbecauseUnityhasnotbeentoldtoletourherodothis.Weneedtoaddtwoelementstoeachcollectableitem:
45
• AColliderComponent,
• ScriptcodetohandletheColliderandupdateplayerhealth,etc.
ThecollectibleitemsintheHierarchyPaneareallPrefabinstances,whicharedisplayedinblue.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:
GototheComponentsmenuandchoosethePickupscriptfromtheThirdPersonPropssubmenu.ThiswilladdthePickupscripttoourPrefab.
SetthePickupTypepropertyintheInspectortoHealthasshownintheimageabove.
Finally,settheAmountpropertyto3orso.Thisistheamountofhealththepickupbestowsontheplayer.
Howmuchhealth?Theheadsupdisplay,or‘HUD’,whichshowstheplayer’scurrenthealthlevel,lives,etc.,canonlyhandleamaximumhealthlevelofsix.Whathappensiftheplayercollectsahealthpickupwhenhealreadyhasafullhealthbar?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.Isuggestusingavalueofaround1530forthesepads.(Thefinishedprojectexampleusesavalueof30.)
Finally,hitPlayandtestthegametomakesureallournewtriggersworkcorrectly.
NOTE ScriptsworksimilarlytoPrefabs:we’vejustaddedalinktothePickupscriptinthePropsfoldertoourPrefab.EditingtheoneinourProjectpanewillalsoaffectanycopiesintheScene.
Goodorganizationisimportantifyouwantyourworkflowtobesmoothandhasslefree.
• UseinstantiatedPrefabswhereverpossible.
• Tryorganizingbyfunctioninsteadoftype.
• UseEmptyGameObjectsascontainers.
Youwillbesurprisedathowmanyassetsareneededforevenasmallscaleproject.
49
TheGUI
TheUserInterfaceGamesusuallyhaveGraphicalUserInterfaces(GUIs),suchasmenus,optionsscreensandsoon.Furthermore,gamesoftenhaveaGUIoverlaidontopofthegameitself.Thiscouldbeassimpleasascoredisplayedinacorner,oramoreelaboratedesigninvolvingicons,inventorydisplaysandhealthstatusbars.
Unity2introducesanewGUIsystemtomakeiteasytobuildsuchGUIsforgamesandthisisthesystemweshalluseforLerpzEscapes.
NOTE TheoldsystemwillremainfortheUnity2.xreleases,butwillbedeprecatedinafutureversionofUnity.Itisnotcoveredinthistutorial.
Unity2'snewGUIsystemPreviously,youwouldtellUnitytodrawabuttonandUnitywouldfireoffrelevantmessagestoyourscriptwhentheuserhoveredoverthebutton,clickedthebuttononit,releasedthebutton,andsoon.
TheoldsystemwasbasedonthetraditionalEventDrivenGUImodel,butUnity2introducesabrandnewGUIsystem,knownasanImmediateModeGUI.IfyouareusedtotraditionalGUIsystems,theImmediateModeGUIconceptmaycomeasashock.
Here'sanexample:
Howmanylivesdowehaveremaining?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(...)conditionalsurroundingthebuttondrawingfunctionreturnstrue,so
Application.LoadLevel()willbecalled.
OtherGUIelementsLabels,Groups,Checkboxes,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
TheIngameHUDOurgameneedsaningameGUItodisplaytheplayer’shealth,livesremainingandthenumberoffuelcellsheneedstocollect.Thegraphicalelementsarealreadyincludedinourprojectfile.
TheGUIishandledwithintheGameHUDscript,whichusesthenewGUIcomponenttolayoutthevariouselements.ThisscriptneedstobeattachedtotheLevelGameObject,whichisusedtoholdScenespecificelements.(WecouldjustaseasilyhaveaddedittotheNearCameraobjectortoitsown'GUI'GameObject;thisismainlyamatterofpersonaltasteratherthanakeygamedesigndecision.)
WewillusetheLevelGameObjecttomanagelevelspecificstatesandotherscripts.
51
TheGUISkinobjectUnity2’snewGUIsystemincludessupportforskinning.ThisgivesyoufullcontroloverthelookandfeelofeveryGUIelement.BuildingyourownGUIskincontentletsyouchangetheshapeofabutton,itsimagery,itsfont,itscolorsanddothesametoeveryotherGUIelement,fromtextinputboxesthroughtoscrollbarsandevenwholewindows.
AsouringameGUIwillbebasedentirelyaroundgraphicalimages,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.GUIsrarelyneedthislevelofoptimizationandtheassetsweareusingarethereforenotoptimized.Ifyouwanttofindoutwhetheranimage’sdimensionsarepowersoftwo,simplybringtheimageupintheInspectorwhichwillletyouknowifitneedssuchoptimizing.
ThisimageisusedtodisplayLerpz'shealthinformation.ThespacetotherightofLerpz'simagedisplayshisremaininglives,whilethecircletotheleftisusedtoshowapiechartofhisremaininghealth.Thepiechartiscreatedbysimplysuperimposingthecorrectimagefromanarrayofsix2Dtextures,namedhealthPie1throughhealthPie6.ThehealthPie5imageisshownbelow:
ThehealthPie5image
NOTE Theseimagesincludealphachannelstodefinetransparencyandtranslucency.
UsingseparateimagesletsussimplydrawtheimagecorrespondingtoLerpz'sactualhealth,insteadofperformingfancycalculationstorotateanddrawsegmentsprogrammatically.
53
ThesecondmajorGUIelementisfortheFuelCellstate,themainimageforwhichisGUIFuelCell:
TheGUIFuelCellimage
Thisisdisplayedinthelowerrightofthegamescreenandwillshowthefuelcellsremainingtobecollectedbeforethelevelisunlocked.
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.healthPie2needstogointoElement1...andsoon.Dragtheimagesintotheirrelevantslots.
FinallyaddtheLerpzTutorialSkinGUISkinintotheemptyGuiSkinslot.Thesettingsshownowlooklikethis:
54
GameHUDscriptsettings.
Ifyourunthegamenow,youshouldseetheHUDappearingovertheplayarea:
TheingameHUD
ResolutionIndependence.
OneproblemwiththeGUIisitssize.Thescreenshotaboveisfroma24"iMacrunningataresolutionof1920x1200.
ClearlyweneedtoscaleourHUDdynamicallyaccordingtothecurrentdisplaysizeandresolution,sohowdoweachievethis?
Unity2'snewGUIsystemincludessupportforatransformmatrix.ThismatrixisappliedtoallGUIelementspriortorendering,sotheycantransformed,rotatedorscaledinanycombinationdynamically.
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,youwillseethattheGUIrescalestofit.
Ifwewished,wecouldhaveourHUDspinaround,flipupsidedownorzoominfromadistance.Forgamemenus,highscorescreensandthelike,thisisausefulfeaturetohaveandwe'llusethistrickforourlevelcompletesequence.
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.DirectivesarecommandswhichgiveUnityinformationoradditionalinstructionsaboutthescript.Thesecommandsaren’tpartofJavascriptassuch,butaimedatUnityitself.Youcanfindthecompleteassembledscriptcodelistedintheappendixsection.
Inthiscase,wewantUnitytorunourscriptinsidetheEditor,sothatwecanseetheresultsimmediatelywithouthavingtostopandreruntheprojecteachtime:
// 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'sagoodhabittosanitycheckanyexternallinksordatainthiswayasitmakesdebuggingmucheasier.)
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”styleelementtouseourbackdropimage.
TheGUI.Label()functiontakesaRectobject.Thisrectangle’sdimensionsarede
rivedfromthedisplay’sdimensions,sothattheimagealwaysfillsthescreen.Theimage’saspectratioisalsotakenintoaccount,sothattheimageiscroppedand/orrescaledtofitwithoutaddingdistortion.
Nextwecometothetitletext.Beforewewritethescriptcodeforthis,weneedtotakeanotherlookatourGUISkinandmakeasmallmodificationtoit:
OurmenuusesthedefaultfontdefinedintheLerpzTutorialSkinasset.Atthemoment,thedefaultstylefordisplayingtextisunsuitableforalargegametitle,sowewilladdanewcustomGUIStyletotheskin,namedmainMenuTitle:
GototheProjectPaneandclickonLerpzTutorialSkintobringupitsdetailsintheInspector.
WewillnowaddaCustomStyletoourGUISkin:
DefiningacustomstyleinourGUISkinobject.
Openup“CustomStyles”andensure“Size”issetto“1”.Youshouldseean“Element0”entry.
59
Open“Element0”andsetitselementsasshownabove.Specifically:settheTextColortotheorangebrownshown.(Don’tworryabouttheelementsnotshowninthescreenshot:theycanbeleftastheyare.)
Finally,wecannowaddtheremainingcodetoourscript:
GUI.Label ( Rect( (Screen.width/2)-197, 50, 400, 100), "Lerpz Escapes", "mainMenuTitle");
NOTE GUIStylenamesarecaseinsensitivewhenyouattempttoaccessthemthroughscripting.Typing“mainMenuStyle”isthesameas“mainmenustyle”.
TheButtons.WenowneedtomodifytheGUIButtonpropertiesoftheLerpzTutorialSkinobjecttoproduceamoreinterestingbutton.
We'reusingthesameGUISkinobjectforthismenuandfortheingameHeadsUpDisplay,or'HUD'.FortheHUD,onlythedefaultfontneedstobechanged.However,fortheStartMenubuttons,wealsoneedtohaveagraphicalimagebehindthebuttontext.Thedefaultbuttondesigndoesn'tfitthegame'svisualstyle,soweneedtochangeit.
TIP IfyouwantyourGUIelementstoreacttoHover,FocusandActiveevents,youmustsetabackgroundimagetoo,eveniftheimageisblank.
ClickontheLerpzTutorialSkinassetintheProjectPanetobringupitsdetailsintheInspectorPane.ChangeittothesettingsshownbelowignoretheotherGUIelementtypes;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,whichneedstobedisplayedwhentheuserselectsthe"Play"button.ThisisbecauseitcantakeafewmomentsforourgameScenetoloadespeciallyifit'sbeingstreamedovertheInternetinsidethewebplayer.
ThisiswhereisLoadingcomesin:
if (isLoading) GUI.Label ( Rect( (Screen.width/2)-110, (Screen.height / 2) - 60, 400, 70), "Loading...", "mainMenuTitle"); }
Again,wemakeuseofthemainMenuTitleCustomGUIstylesothatthetextstylematchesthatofthetitle.(GUIStylesarecaseinsensitive,sotheuppercaselettersarepurelyforreadability.)
TheQuitButton.The"Quit"buttonwillonlyworkifyou'rerunningthegameasastandaloneapp.Ifit'sbeingplayedinawebplayer,DashboardwidgetorinsidetheUnityEditoritself,the"Quit"buttonwillnotdoanything.TheUnitywebplayercannotquitasit'sembeddedinawebpage.Norisitagoodideaforittoclosethebrowser.(Youcouldarguethatclosingthepagethewebplayerisrunningonmightbeanoption,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,accompaniedbyashort,loopingorchestraltune:
TheStartMenuinaction.
Sothat’sit!OurStartMenuisdone.
TIP ThemusicwascreatedusingAppleLoopsfromApple’sOrchestralJamPack,arrangedinGarageband.ThisisahandytoolforMacuserstobuildplaceholdertunes;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’sbuiltintransformmatrixtohandlethescalingforus.
TIP TheGUItransformmatrixcanalsobeusedtoperformanyarbitrarytranslationsyouwish: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’sGUIsystemwillalwaysrendertheseelementsintheordertheyappearinthecode,sothistextwillappearontopoftheshadow.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.
Nogameiscompletewithoutadversaries.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.ThelineitselfisdrawnusingthesamerenderingtechniqueastheTrailRenderercomponent,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.ThisismoreflexiblethanhardcodingtheeffectintheLaserTrapobjectandmakesiteasiertoreusethisassetinotherprojects.
Thescriptdefinestwofunctions,solet’slookattheseinturn:
TheStart()functioninitializestheLineRenderercomponent,storingitsinitiallocation(intheYaxis)forlater,andsettingtheLineRenderer’ssecondvertextomatchthepositiondefinedbylaserWidth.Thisletsuseasilytweakthelengthofthebeamtomatchthewidthofthecorridor.
NOTE WecouldjustsettheendcoordinateofeachlinebyhandintheLineRenderer’sarray,buthandlingthisinthescriptgivesusabitmoreflexibilityshouldwedecidetomakethelaser’sbeamanimateingame.
NowwecometothemainUpdate()function.Thisiswheretheinterestingstuff
happens.
First,wehavethelaserbeammovementtocalculate.WejustusetheMathf.Sin()
functionforthis.WegrabthecurrenttimeusingTime.time,(whichreturnsthe
time,inseconds,sincethegamestarted),multiplyitbyourdesiredanimationspeedandaddinthetimingOffsetvalue.Thisgivesusourpositionalongthesinecurve.Finally,wemodulateitbyourdesiredheightandusetheresultasanoffsetfromourbaseline.
73
Thenextstepistocheckforcollisions.ThescriptdoesthisbycastingarayalongthepathofthebeamandcheckingifithitsanyGameObjectwhichhaveColliderComponentsattachedtothem.
(Thetimebasedtestistheretoallowtimeforareaction.Ifwedidn’tdothis,theplayerwouldloseahealthpointforeveryframeinwhichhe’shitbytheraycast.)
Ifwedohitsomething,wecheckifit’saplayerorenemyand,ifso,sendthe“ApplyDamage”messagetoit.Atthesametime,wealsoinstantiatethehitEffectGameObject,sothatitcanperformitsmagic.ThisistheLaserHitGameObject,whichproducesanenergybursteffect:
Beinghitbyalaserisbadforyourhealth.
Ifyouplaythetutorial,youshouldfindthatLerpzlosesahealthpointifhehitsthelaserbeam.
74
TheRobotGuardsThemobileantagonistsarerobotguards,placedstrategicallyaroundthelevel.WhenLerpzgetswithinrange,theseguardswillhomeinonhimandattempttocausehimharm.
Arobotguard.
Lerpz'senemiesinthisgamearerobotguards.Thisparticularmodelisnowconsidereda'classic',worthyofpreservation.Collectorsandenthusiastscomparethem,unfavorably,withsuchotherclassicgemsofyesteryearastheFordEdsel,AppleLisaandSinclairC5.
Thisparticularmodeliswellknownforthewayitwascunninglydesignedtobesusceptibletoacoupleofwellaimedpunchesatthetorso.Whensoincapacitated,therobotsejectanyitemstheyarecarrying,afterwhichtherobotwillhappilylieonthegrounduntiltheBIOScanreboot,althoughthiswouldonlytakeplacewhentheproximitysensorassertedthattherewasnothinginthevicinity.Thesesecurityrobotsarenowusedasgardenornaments.
Fromtheabove,wecandeterminethat:
• RobotswillhaveafairlyrudimentaryAI.
• Therobotsejectcollectableitemswhenknockeddown.
• Robotswillrespawnwhentheplayercan'tseethem.
75
Seek&DestroyMostgameAIisprimarilyfocusedonmodelingbehaviorratherthanintelligenceassuch.Ourrobotsdeliberatelydisplaylittleactualintelligence,butmerelyreactinpredictablewaystotheplayer'spresence.Thisisnotnecessarilyabadthing:Manygameplayerslikethiskindofbehavior.Itgivesthemapatterntolookforandthusmakesiteasierthelearnhowtodefeattherobots.
TherobotguardsthereforehaveaverysimplebehaviorpatternandthisisreflectedinthemainAIscript,EnemyPoliceGuy.
IdleInthismode,theguardsjuststandtherethinkingrobotthoughts,tickingawayquietlyandwaitingforanintrudertocomeintorange.
ThreatenIfanintrudercomeswithinasetdistanceoftheguard,theguardcomesoutofstandbyandannouncesitsintentionswhilespinningitsbatoninwhatwasadvertisedinthesalesbrochureasa“threateningmanner".
Seek&DestroyOnceactivated,therobotguardswillhomeinontheintruderandtryandattackhim.Iftherobotguardgetswithinstrikingrangeoftheintruder,theguardwillattempttohititwithitsbaton.
NOTE Oneanimationsequenceisusedforbothmodesandisnamed“turnjump”inthemodelandscript.
Struck Iftheplayerstrikestherobotguard,thisanimationisplayed.(“gothit”inthemodelandscript.)
Iftheplayermovesoutsidetherobot'slimitedscanningrange,therobotwillrevertbackto"Idle"mode.
TheabovestatesarehandledbytheEnemyPoliceGuyscript,whichalsodealswithswitchingbetweentheanimationsequences.(Thereweren’tenoughanimationsequencestojustifysplittingthescriptintotwoaswasdonewiththeplayer.)
AddingtheRobotGuardsWeneedafewoftheserobotsonourlevel,so...
OpentheProjectPaneandlocatetheCopperPrefabinsidetheEnemiesfolder.
FindasuitablespotonthelevelanddropthePrefabontotheScene,ensuringitisontheground.(ThePrefabincludesaCharacterControllerComponent.Makesurethelowerendofthecapsulecolliderisjusttouchingtheground,orslightlyaboveit.)
Areaswithfencesorwhichareotherwisemainlyenclosedarebestasthismakesitlesslikelythattherobotwillfalloffthescenery.
76
Ifyouplaythegamenow,youwillseetherobotstandingaroundplayingtheIdleanimation.Wenowneedtoaddsomescriptstomaketheserobotsdosomethingmoreinteresting.
Therearetwoscriptsfortherobotandwe'lladdthemdirectlytotheoriginalPrefab:
SelecttheCopperPrefabtobringitupintheInspector.
DragtheEnemyDamageandEnemyPoliceGuyscriptsontotheCharacterControllercomponentintheInspector.
Ifyouhaven’talreadydoneso,dragtheThirdPersonCharacterAttackscriptontothePlayerobject.Thishandlestheplayer’sendofthepunchingmovement.(Lerpzwon’tpunchrobotswithoutit!)
Ifyouplaythegamenow,therobotshouldreacttoyouifyougettooclosetoit.
BlueSparksOfDeathIftheplayermanagestobeattherobotupandknockhimdown,adeathsequenceisinitiated.Wewanttherobottofalloverinashowerofsparksandliethereuntiltheplayermovesoutofrangebeforeresetting.
Inaddition,whentherobotdies,wewantittospitoutanycollectablesit’scarrying.
Ratherthanaddingyetmorescripting,animationdataandotherelementstoourcurrentPrefab,we’llcreateanewonejustforthedeaththroesofourelectricfoe.
Divide&ConquerWhenourrobotdies,weneedittostopreactingtotheplayerandsuspendthescripts.Thisisnotquiteassimpleasitseems:scriptstendtorunindependentlyofeachother,sowewouldneedtosendoutmessagesandmaintainadedicatedstatevariableineachscriptwhichwouldneedtobecheckedduringeverycycle.
ItismucheasiertosimplyswapoutourrobotPrefabforanotheroneavirtualstuntdouble,builtspecificallyforthepurposeoffallingover,withsuitablespecialeffectsandscriptingtospitoutuptotworandompickups.Sothisisexactlywhatwe’lldo.
77
Arobotguardkeelsover
TheimageaboveshowsthisreplacementPrefabinaction.
ThescriptwhichbringsitintoexistenceisEnemyDamage,solet’stakeacloserlookatit...
WhenanApplyDamagemessageissenttotheGameObject,theApplyDamage()
functioniscalled.Iftherobothassufferedtoomanyhitsinourexample,thehitpointsaresetto3itcallstheDie()function,whichiswherethefunbegins.
TheDie()functionfirstmarksourCopperGameObjectfordestruction.(Thedestruc
tionisnotperformeduntilaftertheUpdate()functionisdone,sothere’snoreason
nottodothisatthestartofthefunction.)
Next,thereplacementPrefabisinstantiatedandtherobot’scurrentpositionincludingthoseofallchildobjectfromheadthroughtorsoandarms iscopiedover.
Thesparklyexplosionisjustanotherparticlesystemasset.ThisisinstantiatedandmadeachildofournewdyingrobotGameObjecttoensureitappearsintherightlocationandalignedcorrectly.
Thenextstep,nowthatwehaveourinstanceinplace,istogiveitapushawayfromtheplayersothatUnity’sphysicsenginecanmakeitfalloverandrollabout.
Finally,weinstantiateamaximumoftwopickups,eachwitha50:50chanceofbeingeitherahealthorfuelcellpickup,andlaunchthemintotheairinarandomdirection.
78
YoucanimplementalloftheabovebyselectingtheCoppergameobjectandalteringthevaluesinEnemyDamagecomponent.Weencourageyoutotryoutdifferentvariations.
DroppablePickups&PhysicsThepickupswhichappearwhenarobotisknockedoverarePrefabsderivedfromtheoriginalswecreatedearlier,butwithaddedparticleeffectsandascript,DroppableMover,whichhandlesmovementandcollisions.
Thescriptisneededbecausethecolliderissetasatriggerforthepickupandthusisignoredbythephysicsengine.Thepickupisgivenaninitialvelocityandthescriptthenusesraycastingcastinganimaginarylinedownwardstocheckifthepickuphashitasurface.Whenitdoes,thescriptissimplydisabled.(Thiscodedoeshaveonedrawback:itonlychecksdownwards,intheYaxis.Thus,thedroppablepickupitemmayendupembeddedpartwaythroughawall.)
Spawning&OptimizationSpawningenemieswhentheyarewithinasetdistanceoftheplayerisanoldtrickdatingrightbacktotheearliestcomputerandvideogames;itremovedtheneedtostoreeachenemy’sstate.Wecanalsousethistricktoreducetheloadontheprocessor.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 Onesideeffectofthistechniqueisthat,ifyouhavekilledarobotandthenmovedoutofrange,itwillreappearasgoodasnewifyoureturntoitslocation.
Howitworks.TheCopperSpawnGameObjectscontainascriptwhichchecksiftheplayerhascomewithinrangeand,ifso,createsaninstanceoftheCopperPrefab.Whentheplayerwalksoutsidethisrange,ourrobotspawningscriptwillautomaticallyremovetherobotfromtheScene.
ThescriptwhichdoesthisisEnemyRespawn.Thisscriptisheavilycommented,butthetwomainfunctionsare:
Start()justcachesalinktotheplayerGameObject’stransformaswe’llneedtomesswithitlater.
Update()Firstchecksiftheplayer’sinrangeandinstantiatestherobotifso.If
not,andtheplayerhasjuststeppedoutofrange,therobotprefabisdestroyed.
TherearealsotwoGizmofunctionsusedintheEditor:
EnemyRespawnalsomakesuseofUnity's"Gizmos"feature.AGizmoisusuallyavisualaiddisplayedonlyintheSceneViewratherthaningame,suchas,inthiscase,asphereshowingthespawnrange.Inthisscript,wehavetwotypesofgizmos:thefirst
80
drawsaroboticon,whichletsusselectthespawnobjectbyclickingonitsiconintheScenewiththemouse:
NOTE Theiconimageiscurrentlystoredinthe[PROJECTPATH]/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,thusprovidingavisualdisplayoftheareawithinwhichtheenemyrobotwillbeinstantiated;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.However,thismightnotbefeasibleifyouwantyourrespawnscriptstobeattachedtoanobjectthatneedstouseacolliderforotherpurposes.
82
Audio&FinishingTouches
IntroductionWhenyoubuyaCDorwatchamovie,thesoundyouhearhasinvariablybeenthroughanumberofstages,thelastofwhichisknownasmastering.Masteringistheartoflisteningtotheaudioasawholeandadjustingvolumelevels,filteringoutfrequenciesandanynumberofothertechnicaltrickstomakethefinalmixsoundasgoodaspossible.Sometimestheprocessfixesobviousflawsinamusicalnumberorsoundtrack,suchasaninstrumentbeingtooloudortooharsh.Atothertimes,theprocessisjustusedtoensurethesoundtrackwillsoundgoodenoughonacheapTVloudspeakerwithoutcompromisingthesoundwhichwouldbeheardfromahighendhomecinemasystem.
Gamesarenodifferent:oncetheroughgameplayisinplace,itmakessensetospendsometimefinetuningit,tweakingandadjustinguntilitisasgoodasitcanpossiblybe.
AudioFinalizingaudioandperformingmasteringdutiesisdifficultwithgames.Gamesareinteractiveratherthanpassive,linearformsofentertainment.Thismeansyouneedtoconsiderhowtheindividualaudioassetsinyourprojectwillinteractand,ifnecessary,performsomepreemptivemasteringandmixingyourself.Essentially,masteringisarealtimetaskwhichneedstobehandledwithinthegamelogicitself.
Inanidealworld,everydeveloperwouldhaveaccesstotheirowntameaudioengineer,butthisistherealworldandmanysmallscaleprojectssimplycannotaffordthis.
Inthischapter,weaddsoundeffectsandtwocutscenestoourgame.
Themostimportantconsiderationisensuringeachsoundinyourprojectsitswellwithallitsfellows.Thisisasubjectiveprocessassomepeoplelikealotofbass,whileotherspreferhigherfrequencysoundsintheirsoundscapes,soit'sagoodideatousealphaandbetatesterfeedbacktogetamixofobjectiveviewsonyourproject'ssound.(Anaudiomonitoringsetupisalsouseful,butthesecanbeexpensiveandannoyingtocolleaguesifyoucan’taffordadedicatedroomforthepurpose.)
Ideally,youneedtouseaudiofromasinglesource,mixedbythesamepairofears,soyougetaconsistentsound.Ifasinthistutorial'scaseyoumustuseabunchofdifferentaudiosources,bepreparedforalotoftweakingofsettingstogetthesoundstositjustrightinthegame'soverallmix.Anoverloudsoundeffectwillbeveryobvioustoplayersandcouldbecomeirritatingifitisheardfrequently.Playtestfrequentlyandbepreparedtobudgetsometimeforthisprocess.
SampleNotesUnityhasafairlysimpleinterfaceforaudio,buttherearesomeimportantthingstoconsider:
• Ensureyoursampleshavesimilarlevels.Thismakesvolumelevelsandrolloffsettingsmuchmoreconsistent.Normalizationcanhelphere,butyoushouldalsoconsiderhowyoursoundswillbemixedtogether.Toomanynoisysoundeffectswillconfusetheplayer.
• Usemonosamplesifyouneedthesoundtobepositionedrealisticallywithinthe3Dworld.
• CompresssamplesusingtheOggVorbiscompressionsystem.(Unitycandothisforyou.)
• Ifbuildingfortheweb,youshouldalwayscompressyoursoundsusingOggVorbis.
• Checkthe"Decompressonload"boxforshorter,frequentlyusedsoundeffects.
• Onlyusestereosamplesforlongmusicalpieceswhichdonotneedtobepositionedspatiallywithinthescene.Suchsamplefileswillalwaysbeplayedbackastheyare,atthedefaultAudioListenervolume.
AddingSoundtoLerpzEscapes!Wehavealreadyaddedafewspoteffectstothegame.Inthischapterwewilladdsomemoreaudiotoourgameandcompletetheprocess.
Inmanygamegenres,soundeffectscanbeobtainedfromrealsources,ortakenfromasoundeffectslibrary.However,LerpzEscapes!needssomesoundeffectswhichcannotbeobtainedsoeasily.Pointingamicrophoneatapassingspacecraftorconvenientrobotguardisnotanoption,sowewillneedtothinkcreativelyaboutthese.
84
Wewillnotaddeverypossiblesoundeffecttothegameinthischapter;onlyenoughtodemonstrateUnity'saudiofeatures.Onceyouhavereadthroughthischapter,youwillbeabletoaddadditionalsoundeffectsonyourown.
Thecompletelistofsoundeffectsthegameneedsis:
ThePlayer:• Walking/Runningsounds;
• Attackingsound;
• Gettinghitsound;
• Dyingsound;
• Jetpackthrusterssound;
TheRobotGuard:• Idlesound;
• Attackingsound;
• Gettinghitsound;
• Dying/Explodingsounds;
TheCollectables:• FuelCellcollectedsound;
• Healthcollectedsound;
Environmentalsounds:• Along,loopedsoundsampletogiveasenseofambience.
• Awhooshsoundforthejumppads.
SpaceshipImpoundFence:• "Active"sound;
• "Shutdown"sound;
TheSpaceship:• Atakeoffsoundtoplaywiththecutsceneanimation;
Wehavealreadyaddedafewsoundeffectstoourgame,butwestillneedtoaddoverfifteenmore.Someoftheseareobviousfoleysounds,suchastheplayer'sfootstepsandthejetpackthrusters.Someoftheothersamplesarerepurposedfoleysounds.(ThepickupFuelsound,forexample,isjustthesoundofagolfballbeingstruckbyagolfclub.)
85
Thesesoundscanbefoundinalmostanysoundeffectslibrary.Apple'sownGarageBand,LogicStudio8andSoundtrack/SoundtrackProsoftwareincludesuchsoundsandtheselibrarieswerethesourceformostofthesoundeffectsinthistutorial.Manymoresuchsamplesareavailableonlineoftenforfree!
AmbientSoundsOurgameissetinanenvironmentwayabovethecloudswithanimpressiveviewofnearbyplanets.Suchanexposedlocationneedsasuitablyoutofthisworldsoundscapetoimprovethesenseofimmersioninthegame.
ThetutorialprojectincludesaloopedambientsoundsamplenamedsceneAtmosphere.ThisisastereoOggVorbissamplecreatedbythrowingsomeold,monoBBCRadiophonicWorkshopsoundeffectsfromthe1960satGarageBand,andcombiningthemintosomethingthatsoundedsuitablyalien.
ThesampleisaddedtotheNearCameraobjectintheHierarchyPane.
DropthesampleontotheNearCameraobject.UnitywillautomaticallycreateanAudioSourcecomponent.
Adjustitssettingsasshownbelow:
AddingthesceneAtmospheresoundtotheNearCameraobject.
TheAudioSourcecomponentincludesbasiccontrolswhichallowustotellUnityhowtodealwiththesoundeitherthroughtheInspectororthroughscripting.
Inthisinstance,wesetthePlayOnAwakeswitchsothesoundstartsplayingautomatically.Thevolumelevelisdeliberatelysetlowasthisisambient,backgroundsoundandwedon'twantittodistractfromtheothersoundsinthegame.
86
Let’stakeaquicklookatwhattheremainingsettingsmean:
ThePitchsettingdefineshowfastthesampleplays,with1beingthenormalspeed.Thealgorithmusedisabasicone,similartoataperecorder,sothereisnotimestretchinginvolved;asettingof2herewouldplaythesampleattwiceitsnormalspeed,doublingitseffectivepitch.
TIP ThePitchsettingisusefulforoneshotsoundeffectsasyoucanadjustthevalueslightlywitheachplaybacktoaddvarietytothesounds.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’sitcomingfrom?
87
It’scomingfromUnityitself.ThisisaconveniencevariableoneofanumberofsuchvariableswhichsimplypointsattheAudioSourceComponentattachedtowhateverGameObjectthescripthappenstobeattachedto.Aswehaven’taddedoneoftheseComponents,theaudiovariablewillbenull,sothescriptskipstheplayback.
ClickononeoftheJumpPadinstancesinourScenetobringitupintheInspector,
DragthejumpPadsoundeffectontotheInspector.(ThiswillautomaticallycreateanAudioSourcecomponentforus.)
ApplythechangetotheoriginalPrefab,soallourJumpPadscansharethesamesoundeffect.
ExperimentandtweaktheAudioSource’ssettingsuntilyou’rehappywiththesoundeffect.
CollectablesThecollectableitemsaretheeasiesttodealwith.ThesesoundeffectsarepickupFuelandpickupHealthforthefuelcellsandhealthcollectablesrespectively.Addingtheseeffectsissimplicityitself:thePickupscriptalreadyhassupportforaudio;wejustneedtodroptherelevantsoundsampleintothescript'ssoundeffectslotineachpickuptype'sPrefab.
TheimagebelowshowstheFuelCellPrefabInspectordetailsforoneoftheinstancesinthelevel,withthepickupFuelsoundeffectaddedtothePickupscript'sSoundvariable:
88
SettingthefuelCellPrefab’ssoundeffect.
AddtheaudioeitherbydroppingthepickupFuelsoundeffectontotheSoundvariable'sslot,orbypickingthesoundeffectdirectlyfromthelistofavailablesampleswhichyoucanbringupbyclickingthesmalltriangletotherightoftheslot.
SettheSoundVolumeto2,tomakethesoundeffectstandout.
ApplythesameprocessfortheHealthpickuptoo,usingthepickupHealthsampleinstead.
TIP YoucanaddthesoundeffectdirectlytothePrefabtosavetime.
Ifyouplaythegamenow,eachcollectableitemwillnowplaytheappropriatesoundeffectwhenyoupickitup.
89
TheImpoundFenceTheforcefieldthatsurroundsthespaceshipshouldmakeahumming,fizzingnoisewhileitisactive.AsuitablesoundeffectwascreatedusingGarageBandbycombiningsomeloopedambienttexturestogetherandexportingtheresultingsoundtoafile.
TIP Apple'sGarageBandonlyexportstotheAAC(".M4A")soundformat,soAudacitywasusedtoconvertthistotheuncompressed,monoAIFFaudiofileusedintheproject.
ThesampleisnamedactiveFence.
GototheHierarchyPane,openlevelGeometryandfindtheimpoundFenceobject.
DroptheactiveFencesounddirectlyontothisobject.ThiswilladdanAudioSourcecomponenttotheimpoundFenceobject.
Finally,changetheAudioSource'ssettingstoreadasfollows:
ImpoundFenceAudioSourcesettings.
ThePlayerLerpzhimselfmakesnosoundsatthemoment.Addingsoundeffectsmakessense,butwhichones?
Thesoundeffectswewillimplementinthistutorialare:
• Apunchsound;
• A“struck”sound,playedwhenLerpzishitbyarobot;
• Asoundeffectforthejetpackthrusters;
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,youwillseetheseoneshotsoundsappearingbrieflyintheHierarchyPane.Thisisnormalbehavior.
Strucksound&ScreamsoundTheThirdPersonStatusscripthandlestwomoresoundeffects:theoneplayedwhenLerpzisstruckbyanenemy,andtheoneplayedwhenLerpzdies(justbeforeheis
91
respawnedorthegameends).Bothsoundeffectsarehandledthesamewayasthe“lerpzPunch”soundeffectweaddedearlier.
AddboththeLerpzStruckandLerpzScreamSFXsoundsasshownbelow:
AddingtheStruckSoundandDeathSoundeffects.
TheJetpackThejetpackisalooped,ratherthanaoneshot,soundeffect,sowe’lladdthesoundeffecttothePlayerGameObjectdirectlyasanAudioSourceComponent.TheJetPackParticleControllerscriptwillautomaticallycreateanemptyAudioSourcecomponent,butweneedtoaddtheaudiofiletoit:
DragthethrusterSoundaudiofileontotheAudioSourcecomponentinsidethePlayerGameObject.
EnsuretheAudioSourcesettingsareasshown:
92
Jetpackaudiosettings
Therearetwosectionsofscripttohandlethissoundeffect,bothofwhichareintheJetPackParticleControllerscript.ThefirstsectiongoesintheStart()functionand
initializestheAudioSourcecomponent:
audio.loop = false; audio.Stop();
Thesecondchunkisfurtherdowninthesamefunction:
if (isFlying) { if (!audio.isPlaying) { audio.Play(); } } else { audio.Stop(); }
TIP TheaudiovariableiscreatedbyUnityitself.ItisalwayssettopointattheAudioSourceComponentoftheGameObject.
93
Thiscodeisselfexplanatory.TheneedtotestifthesoundeffectisalreadyplayingisbecausethePlay()functionwillalwaysstartplayingthesoundeffectfromthebeginning,regardlessofwhetheritisalreadyplaying.Thismeanswe’dhearastutteringsoundasUnitywouldrepeatedlyrestartthesoundsampleeverytimethePlay()
functioniscalled.
TheRobotGuardsTheseguyshaveanumberofscripts,someofwhichalsohaveaudiosamplesasproperties.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,addtheMetalHitsoundeffecttotheStruckSoundpropertyoftheEnemyDamagescriptcomponent.
(TheresultingviewintheInspectorPaneisshownonthenextpage.)
94
TheMetalHitsoundisplayedintheApplyDamage()functionwithintheEnemyDamagescript.Thecodewhichdoesthisisshownbelow:
if (audio && struckSound) audio.PlayOneShot(struckSound);
AswiththeJetPack,theaudiovariableweusehereisactuallyashortcutvariable
createdbyUnityitself.ItistheequivalentofaGetComponent (AudioSource)
functioncall.TheupshotisthattheAudioSourcecomponentattachedtotheCopperPrefabisusedtoplaythesoundeffectforus,avoidingtheneedtoinstantiateatemporaryaudiosourcecomponentforthepurpose.
NOTE ThesametrickisusedforsomeofthePlayersoundeffects.
CopperPrefabInspectorsettingsafteraddingtheaudiofiles.
95
CutScenesCutscenesprovideausefulwaytofillintheplayeronaneventorstoryelementwhichtheyneedtoknow.Ideally,theneedtocutawayfromtheplayershouldbekepttoaminimum.Forthisreason,wehaveonlytwocutscenes.
Thefirstoccurswhentheplayermanagestocollectalltherequiredfuelcellsinthelevel,thusunlockingthespaceship.Thiscutsceneappearsusingapictureinpicturetechnique,sothattheplayercancontinueplayingwhilethesequenceplaysout.
Oneextremelypragmaticreasonfornottakingoverthewholescreenwiththiscutsceneisthatwe’dotherwisehavetofreezeallthegameelementsrobots,playercontrols,etc.whilethecutsceneplays,becausetheplayerwouldbeeffectivelyblinduntilthescenecompletes.
NOTE Itisstillpossibletogetatthespaceshipifyouclimbontothenearbycrates,butthespaceshipisstilllockeddownatthispointandwon'ttakeoff.(Themeshcolliderisn’tchangedtoaTriggertypeuntiltheimpoundfenceisunlocked.)
Thesecondcutsceneoccurswhentheplayertouchesthespaceshipafterthefencehasbeendisabled.Inthisscene,whichisplayedfullscreen,weseethespaceshiptakingoffandflyingofftofreedomandnewadventuresbeforeswitchingtotheGameOversequence.
Let’slookatthefirstsceneindetail...
UnlockingtheimpoundfenceWefirstcameacrosstheImpoundFencepartofthelevelwhenweaddedsomeanimationtoit.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.Thisiswhereboththecutscenesarehandled(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’llneedforthesecondcutscenetoo.
Next,addthefollowinglineofscriptcodetotheAwake()function:
levelGoal.GetComponent(MeshCollider).isTrigger = false;
Thatlineiscrucial.Itstopsthesecondcutscenefromtriggeringprematurely.WiththeisTriggerswitchturnedoff,thespaceshipbecomesjustanotherpartofthesceneryaslongastheimpoundfenceisstillactive.
Nowfortheunlockingsequenceitself.
AddthefollowingfunctiontotheLevelStatusscript.(I’llexplainitaswego.)
function UnlockLevelExit(){ mainCamera.GetComponent(AudioListener).enabled = false;
UnitysupportsjustoneandonlyoneAudioListenercomponentinanyoneScene.UsuallythisisattachedtothemaincameraintheScene,butwe’reusingmultiplecamerasinourScene,soweneedtoensureweonlyhavetheoneAudioListeneractiveatanyonetime.Wewanttohearour“fencedisabled” soundeffects,sowe’llbrieflyenabletheAudioListeneronourcutscene’scamerahere.
Next,weneedtoactivatethecutscenecameraandenableitsAudioListenercomponent:
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’lldothisprocedurallybyusingscriptcodetoachievetheanimation.Thenextfewlinesperformthissequence.Thefirstlineaddsadelaybeforethesequencebeginstogivetheappearanceofthecutscenetimetoregisterontheplayer’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,beforeshuttingoffourcutscenecameraandrestoringtheAudioListenercomponentonourNearCamera:
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;}
Creatingthecutscenecameraitselfisournexttask.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.Disablingthecameraissimple:justunchecktheboxnexttoCutSceneCamera1,rightatthetopoftheInspector.
Also,takecaretosettheNormalizedViewPortRectsettingsasshownabove.Thesedefinethecamera’soutput’spositiononthescreensothatitappearsinthetoprightcornerofthedisplay.Thecamera’sdepthisalsosetto10,whichishigherthanthatoftheNearCamerasothecutscenewillappearontopofthemaingameimagery.
Weneedtotestthesequence,sosettheLevelStatusscript’spropertiesasshownbelow:
SettingtheLevelStatusscriptproperties.
101
ThespaceShipobjectisthespaceshipmodelsittinginsidetheimpoundfence.
TIP I’vetemporarilysettheItemsNeededvalueto2inthescreenshotabove.Thisletsmetestthecutscenebycollectingonlytwofuelcansinsteadofwastingtimerunningaroundmostofthelevelfirst.Remembertoresetittoahighernumbersay,20whenwe’redone.
Ifyouplaythegamenow,youshouldseethecutsceneappearasshownbelow.
Ourfirstcutsceneinaction.
NOTE Theframespersecondcountervisibleinboththemainviewandthecutsceneinsertiscoveredinthechapteronoptimization.
Thelastcutsceneisalittlemorecomplex.Weneedthespaceshiptotakeoffandflyaway.Wecoulddothisusingscripting,butit’smucheasiertouseanAnimationClipforthisinstead:
ClickonthespaceShipmodelinsidetheimpoundfencetoselectit.(OrclickonitintheHierarchyPane)
NowdragtheShipAnimationAnimationClipfromtheAnimationsfolderintheProjectViewandaddittotheSpaceshipAnimationpropertyoftheAnimationcomponentintheinspector.
Youwillnowseeananimationcomponentinthespaceshipinspector.Ifyouhitplaynowyouwillseetheshiptakingoff.Howeverweonlyneedtheshiptotakeoffwhentheleveliscomplete.Wewilldothiswithascriptinaminute.HoweverUnity,bydefault,assumesyouwantanimationstoplayautomatically.Wedon’twantthis,so,withthegamestopped:
Disablethe“PlayAutomatically”checkboxofthespaceShipAnimationcomponent.
102
ThespaceShipobject’sanimationsettings.
Thenextstepistocreateoursecondcutscenecamera:
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.
Nowforthecutsceneitself.Thisisalittletrickierthanourfirstcutscenebecausethemessagesusedtotriggerthesceneneedtoberelayedalongachain:
TheinitialtriggerhappenswhenthePlayertouchesthespaceShipmodel.(Ifthefirstcutscenehasplayedthrough,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.LevelStatusiswheretheactionisasthelevelcompletionanimationissomethingonlythelevelrelatedscriptsshouldknowabout.
AddthefollowingLevelCompleted()functiontoLevelStatusnow(I’llex
plainitaswego):
function LevelCompleted(){
First,wehavetodothesameAudioListenerswitchoveraswedidforourfirstcutscene:
mainCamera.GetComponent(AudioListener).enabled = false; levelCompletedCamera.active = true; levelCompletedCamera.GetComponent(AudioListener).enabled = true;
Next,wewanttogivetheillusionthattheplayerisinsidethespaceship,sowe’llhide
106
him.Todothis,we’llsenda“HidePlayer”messagetothePlayer’sThirdPersonControllerscript.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); }
Nowwestartthetimelinebasedanimationwerecordedearlierandwaitforittofinish:
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,whichwillbecalledautomaticallybyUnitywhenthescriptisloaded.
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’renowatthewrappingupstage.Optimizingissomethingdoneneartheendofaproject,onceallthekeyelementsareinplaceandnaileddown.What,whereandhowyouoptimizeyourprojectverymuchdependsonyourproject’sdesignandcontent,sothischapterisprimarilyadiscussioncoveringthemorecommontypesofoptimization.
WhyOptimize?Unityprojectsareoftentargetedatoldercomputersthan,say,amainstream,bigbudgetgame.Puzzlegames,casualtitlesandotherprojecttypesmayberequiredtorunonanythingfromaG4iBookwithamere256MbofRAMandanancientgraphicschipset,throughtoacurrentIntelbasedMacProstuffedtothefaceplateswithmemoryandahighendgraphicscard.
Forthisreason,weneedtoconsideroptimizingourprojectsforfinalrelease.Inourcase,we’vealreadyoptimizedtheenemyrobotsbymakingthempopintoexistenceonlywheninrange,sothat’salreadycovered.However,therenderingofthescenecanbequiteslowandthisisworthlookingatinmoredepth.
OptimizingRendering:MonitoringFramesPerSecondThebestwaytodeterminewhetheryourgameneedstobeoptimizedistofindouttheframeratethenumberofimagesbeingrenderedeachsecondanddisplaythis.Thelowerthenumber,theslowerthegameisrunning.
Youmayhavenoticedanumber,ortheletters“FPS”,insomeofthescreenshotsinthistutorial.Thisisbecauseoneimportant,butmercifullyveryshort,scriptisrunning
GamesmayberequiredtorunonanythingfromaG4iBooktoacurrentIntelbasedMacProstuffedwithmemoryandahighendgraphicscardortwo.Forourgamestorunonboth,weneedtooptimize...
whichwe’lllookatnow:aFramespersecondreporterscript.ThescriptisnamedsimplyFPSandcanbefoundintheScripts>GUIfolderoftheProjectPane.
Thescriptisthoroughlydocumented,soIwon’tcoveritindetailhere,otherthantoaddthatitrequiresaGUITextComponent.(ThisisaUnity1.xGUIComponenttypeasopposedtothenewUnity2GUI.)
WiththisFPScounter,itiseasiertogetagoodideaofwhereoptimizationsneedtobemade.
NOTE TheFPSscriptisoflimitedvaluewhenrunningthegameintheUnityEditor.ThisisbecausetheEditorrenderermaybelockedtotheframerateofthedisplay.ItalsohastoupdatetheSceneViewandgenerallyperformsmoreerrorcheckingwhileplayingScenes.Formoreaccurateresults,useaStandaloneBuildofyourproject.
MakingsenseoftheStatsdisplayNewtoUnity2isa“Stats”buttonabovetheGameView.Ifweenablethis,wecangetsomeadditionalmetricsonourgame,whichcanhelpdeterminewhetherthereareissuesofpolygoncountorotherobjectcomplexitytoresolve.
TheStatspanelinaction.
Thesestatisticsarebasedonwhatthecameraisrendering,somovingaroundthescenewillchangemanyofthestatistics.Theimportantelementsare:
DrawCallsthenumberofrenderpasses.Elementsinascenemayneedtoberenderedmultipletimes:forshadows,multipleCameras,renderingtotextures,pixellights,andmore.Complexshaderscanalsocauseadditionaldrawcalls,particularlyifreflectionorrefractionisbeingcalculated.
111
Tris Thenumberoftrianglesbeingdrawn.
All3Dmodelsarebuiltupfromtriangles.Thefewerthetriangles,thefasterthey’llrender.Round,curvedobjectswillgenerallyusemoretrianglesthanbasic,straightedgedshapeslikecubesandplanes.
Verts Thenumberofverticesbeingsenttothegraphicschip.
Avertexisapointin3Dspace.Themoreverticesyoucanshareacrosstriangles,themoreofsaidtrianglesyoucanrenderandthusthemorecomplexyourmodelscanbe.
UsedTexturesthenumberoftexturesusedtorenderwhatyousee.
Materialscanuseoneormoretextures,dependingonhowthematerialisdefinedandtheshaderscriptitisusing.Theshaderdefineshowthetexturesarecombined,producingeffectssuchasbumpmapping,glossyshinehighlights,reflectionsandrefraction.
TIP Particlesystemsusetwotrianglesforeachparticle,andatleastonetexture.(Texturesareusuallysharedbyalltheparticlesinaparticlesystem.)Itisveryeasytogetcarriedawaywiththeseeffects,butyoushouldtakecarenottooverdoit.
RenderTexturesthenumberofcamerasoutputtingtoatextureratherthandirectlytothedisplay.Thisisn’tasclearcutasyoumightexpect...
Rendertexturesareusedtoachieveanumberofeffects,suchaspostprocessingeffects,aCCTVscreendisplayinganotherareaofalevel,ortoproducereflectioninwater,amirrororglassrefractioneffects.Inaddition,mostshadowsarealsoproducedusingthistechnique,soyouwon’tnecessarilyseeadditionalcamerasinyourSceneView.
OptimizingRendering:TheTwoCameraSystemIfyouhaveplayedwiththecompletedproject,youmayhavenoticedbynowthatthereare,infact,twoNearCameras:aNearCameraandaFarCamera.Thistwocamerasystemreducestheamountofrenderingneededforeachframe.
TheNearCamerarenderseverythingwithinacloserange;inthiscase,from0.4to50units.
TIP Aunitcan,intheory,beanyarbitrarylengthyoulike,butmostdeveloperstendtostickwith"1unit=1meter"forthesakeofsanity.Animportanttipistomakesureyourartist(s)areawareofthescaleyouareusing.
TheFarCamerarendersfromthe50unitsmarkthroughtoaround500units.However,itonlyrendersasubsetoftheScenedata.ThesubsetisdefinedbyLayers.Each
112
objectintheSceneisgivenaLayertoliveon.TheNearCamerarendersallelementsregardlessoftheirlayer,butasyoucanseefromthescreenshotbelow,theFarCamerahasbeentoldtoignorethoseitemsinthe“cameraTwo”or“cameraTwoIgnoreLights”Layers.We’dalsolikeittoignorethefuelcansandhealthpickups.Thesehavethe“noShadow”Layer,sothattooisuncheckedforthiscamera.
NOTE Uncheckingthe“noShadow”Layeralsomeansitwon’trendertheplayereither,whichisfineastheplayerisunlikelytobethatfarawayfromtheNearCamera.
TheFarCameraCullingMasksettings.
NOTE TheFarCameraisalsothecamerawheretheSkyboxisrendered.(SeetheClearFlagssettingintheshotabove.)TheNearCamera’sClearFlagssettingis“DepthOnly”,sothatitscontentissuperimposedoverthatoftheFarCamera.TheFarCamera’scontentisrenderedfirst.
ThisselectionisdefinedbytheCullingMaskpropertyofthecameracomponent.Tickedlayersarerendered;untickedlayersarenot.YoucandefineaLayerintheInspectorandassignonetoanyGameObject.
Youcanseethisoptimizationinactionwiththerobotguardsandthecollectableitems.Ifyoumovetowardsoneoftheseitems,youwillseethesceneryarounditisalwaysrenderedwhiletheitemitselfappearsrelativelyclosetotheplayer.
113
Endoftheroad.
TheRoadLessTravelledAtthetimeofwriting,thistutorialholdsthedubioushonorofbeingthelongesteverproducedforUnity.Wehaveseenhowtobuildasinglelevelofagamebasedaroundthe3DPlatformergenre,butevenafterallthesepages,wehavebarelyscratchedthesurfaceofwhatispossiblewithUnity2,orevenwiththisparticulargenre.
Ourjourneytogetherisdone:It’stimeforyoutotakethestabilizerwheelsoffthebicycleandcontinuealone,butbeforeyougo,herearesomesuggestionsforwhattotrynext...
SuggestedImprovementsLerpzEscapeshasbeenleftdeliberatelyunfinished.WehaveaverybasicStartMenuandaGameOverscreen,butthere’sonlytheonegamelevelanditisclearlyintendedtobethelastoneinthegame.Howcoulditbeimproved?
FixingthedeliberatemistakesYes,therearesomeminorissueswiththegameasitstands.Thesehavebeendeliberatelyleftinplacetogiveyouachancetohoneyourskills.Theyare:
• Ifyoukillarobot,butarerespawnednearbybeforeyouhavemovedoutofrange,anewrobotwillappear,buttheoldonewillremain.
• TheLaserTrapsdon’tkicktheplayeraway,soitispossibletoloseallyourhealthratherquickly.
Whatwehavelearned.
Wheretogonext.
Bothcanberesolvedbyapplyingwhatyouhavelearnedinthistutorial.
MorelevelsTheProjectPaneincludesa“BuildYourOwn”foldercontainingalltheindividualassetsusedtobuildthelevel,soaddingnewlevelsshouldnotbedifficult.
YouwillneedtousetheDontDestroyOnLoad()functionsothatyoucancarry
gamestateinformationbetweenthelevelssuchasthecurrentscore,livesremaining,etc.
MoreenemiesThegameonlyhastheoneambulatoryenemyintheformoftherobotguards.Whynotaddsomemore?ThisisagoodwaytoensureyouhaveunderstoodtheanimationandAIaspects.ItwillalsohelpyougetafirmgraspofbuildingmodelsandimportingthemintoUnity.
AddscoringLerpzEscapeslacksascoresystem.Addingoneisnotdifficult,butaddingvisualeffectswhentheplayerdoessomethingworthyofincreasingtheirscorecanbeaschallengingasyoulike.(And,ofcourse,you’llwanttokeeptrackofthescoreacrossScenes.)
AddanetworkedhighscoresystemTheUnity2bringssolidnetworkingsupporttothetable,aswellasintegrationwithwebsites.Whatbetterwaytoshowoffthanuploadingyourhighscoretoacentralserversoyoucangloat?Thisisagoodwaytowrapyourheadaroundnetworkingbasics.
AddmultiplayersupportAddingnetworkedmultiplayergamesupportisprobablythetrickiestthingyoucandowithanygame.Naturally,Unitycanhelpheretoo,butyouwillneedtogetdownanddirtywithscripting.Thisisagood,advancedlevelimprovementtoadd.
FurtherReadingThefirstplacetolookformoreinformationis,asalways,Unity’sowndocumentation.
Therearealsomanytutorials(includingvideoprimers)ontheUnitywebsite:http://unity3d.com/support/documentation/
Inaddition,theUnifyWikiisanexcellentsourceofusercontributedinfo: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
Recommended