Tải bản đầy đủ (.pdf) (269 trang)

Ebook 3D game programming for kids (Second edition): Part 2

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.53 MB, 269 trang )

Chapter14

Project:ThePurpleFruitMonster
Game


Inthischapterwe’llmakeatwo-dimensionaljumpinggame.Theplayerwilluse
keyboardcontrolstomakethePurpleFruitMonsterjumpandmovetocapture
asmuchrollingfruitaspossible,withouttouchingtheground.Itwillendup
lookingsomethinglikethis:

Thismightseemlikeasimplegametowrite,butwe’regoingtousealotofthe
skillsandknowledgethatwe’vebeenbuildingupinthebook.Andtogetthe
jumpingandrollingandcapturing,we’regoingtointroduceawholenewlevel
ofsophisticationtoourcode.Thisisgoingtobeafunone!


GettingStarted
Startanewprojectin3DE.Choosethe3D starter project (with Animation)template
andnamethisprojectPurple Fruit Monster.Donotusethetemplatewithphysics
forthisproject—we’llusethatinlaterchapters.

Let’sMakePhysics!
ThisgamewillneedtwoJavaScriptcodecollectionsandsomesettingstogo
alongwiththem.Attheverytopofthefile,addtwonew<script>tags:
<body></body>
<script src="/three.js"></script>
① <script src="/physi.js"></script>
② <script src="/scoreboard.js"></script>



We’regoingtousecodetosimulatereal-lifemotionlikefalling,rolling,and
colliding.WeusethePhysijs(physics+JavaScript)codecollectionsowe
don’thavetowriteallthephysicscodeourselves.



Tokeepscore,weagainusethescoreboardcodecollection.

Atthetopofthecodefromthe3D starter projecttemplate,justbelowthe<script>
tagwithoutansrcattribute,makethechangesnotedbelow.
// Physics settings
① Physijs.scripts.ammo = '/ammo.js';
② Physijs.scripts.worker = '/physijs_worker.js';
// The "scene" is where stuff in our game will happen:
③ var scene = new Physijs.Scene();
④ scene.setGravity(new THREE.Vector3( 0, -250, 0 ));
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.8);
scene.add(light);


AsettingthatenablesPhysijstodecidewhenthingsbumpintoeachother.



“Worker”codethatrunsinthebackground,performingallofthephysics


calculations.



InsteadofaTHREEscene,weneedtouseaPhysijsscene.



Evenwithphysics,wewon’thavegravityunlessweaddittothescene.In
thiscase,weaddgravityinthenegativeYdirection,whichisdown.

Justonelastbitofsetupremainstogetourscenetoactivelysimulatephysical
activity.We’llwaituntilafterweaddsomeobjectstothescenebeforeworking
onthat.First,let’sconvertfroma3Dscenetoatwo-dimensionalscene.


VectorsAreDirectionandMagnitude
We’reusingTHREE.Vector3tosetgravity.We’re
goingtousethesealotinthischapter.Ifyousawthe
firstDespicableMemovie,thenyoualreadyknow
whatthisis!
ThebadguyinthatmovieisVector.Hechosehis
supervillainnamebecauseavectorisanarrowwith
directionandmagnitude(Oh,yeah!).Thatmeansa
vectorincludestwopiecesofinformation:the
directioninwhichitpointsandhowstronglyit
pointsinthatdirection.
Thevectorthatdescribesgravityinthisgamepoints
inthenegativeYdirection(down).Ithasahigh
magnitude(250),whichmeansthatthingswillfall
downfairlyquickly.

Let’sMake2D

Themostimportantchangetomakefora2Dgameistouseanorthographic
camera.BackinChapter9,What’sAllThatOtherCode?,wetalkedabouttwo
usesforthesecameras:longdistanceviewsand2Dgames.Weusedan
orthographiccameraforthelongdistancesofspaceinChapter13,Project:


PhasesoftheMoon.Nowweuseonefora2Dgame.
StillworkingabovetheSTART CODINGline,commentout(ordelete)thecodefor
theusualperspectivecamera.ThenaddanOrthographicCameraasshown.
»
»
»
»
»

// var aspectRatio = window.innerWidth / window.innerHeight;
// var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
var w = window.innerWidth / 2;
var h = window.innerHeight / 2;
var camera = new THREE.OrthographicCamera(-w, w, h, -h, 1, 10000);
camera.position.z = 500;
scene.add(camera);

Oneotherchangethatwe’llmakeisabluesky.Tochangethecoloroftheentire
scene,setthe“clear”color—thecolorthat’sdrawnwhenthesceneisclearof
anythingelse—toskyblue.
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
» renderer.setClearColor('skyblue');
document.body.appendChild(renderer.domElement);


Withthat,we’rereadytostartcodingourjumpinggame.


OutlinetheGame
Let’sthinkabouthowwecanorganizeourcode.Tohavemadeitthisfarinthe
book,you’vewrittenalotofcode.Attimes,itmusthavegottendifficultto
movethroughthecodetoseewhatyou’vedone.You’renotthefirst
programmertorunintothisproblem,andyouwon’tbethelast.Thankfully,you
canlearnfromthemistakesofprogrammersbeforeyou.


KeepYourCodeOrganized
Programmingishardenoughonitsown.Don’tmake
itharderbywritingmessycode.Organizingcode
doesn’tmattertoomuchwithshortprograms.But
codegrowsasnewstuffisadded.Organizedcode—
indentedandwithfunctionsdefinedintheorderthat
theyarecalled—iscodethatcangrow.

Oneoftheeasiestwaystoorganizecodeistotreatitalittlebitlikewriting.
Whenyouwriteanessay,ithelpstostartwithanoutline.Afteryouhavethe
outline,youcanfillinthedetails.
Whenorganizingcode,ithelpstowritetheoutlinefirst,thenaddthecodebelow
it.Sincewe’reprogramming,ouroutlinesarealsowrittenincode.Typeinthe
following,includingthedoubleslashes,belowSTART CODING ON THE NEXT LINE.
//var ground = addGround();
//var avatar = addAvatar();
//var scoreboard = addScoreboard();


Thisoutlinedoesn’tincludeeverythinginthegame,butit’salotofit.The
groundwillbetheplayingarea.Theavataristheplayerinthegame.The
scoreboardwillkeepscoreanddisplayusefulinformation.
ThedoubleslashesatthebeginningofeachofthoselinesintroduceaJavaScript


comment,whichwefirstsawinCodeIsforComputersandHumans,Comments
AreOnlyforHumans.ThismeansJavaScriptwillignorethoselines.Thisisa
goodthingsincewehaven’tdefinedthosefunctionsyet.
Programmerscallthis“commentingout”codesoitwon’trun.Programmersdo
thisformanyreasons.Here,we’redoingittooutlinecodewithoutcausing
errors.
We’lldefinethesefunctionsinthesameorderastheyareinthecodeoutline.
Thismakesiteasiertofindcode.Bylookingatthecodeoutline,weknowthat
theaddGroundfunctionwillbedefinedbeforetheaddAvatarfunction,whichwill
befollowedbyaddScoreboard().Thefasterwecanfindcode,thefasterwecanfix
itoraddthingstoit.Whenyouwritealotofcode,trickslikethiscanreallyhelp
keepthingsstraight.
Afterwebuildeachfunction,we’llcomebacktothiscodeoutlinetoremovethe
doubleslashesbeforethefunctioncall—we’ll“uncomment”thecallswhen
they’reready.
Let’sgetstartedwritingthecodethatmatchesthisoutline.


AddingGroundfortheGame
ThefirstfunctioncallinourcodeoutlineistotheaddGroundfunction.Justbelow
thecodeoutline(afterthecommented-out//addScoreboard()line),definethat
functionasfollows:
function addGround() {
var shape = new THREE.BoxGeometry(2*w, h, 10);

var cover = new THREE.MeshBasicMaterial({color: 'lawngreen'});
var ground = new Physijs.BoxMesh(shape, cover, 0);
ground.position.y = -h/2;
scene.add(ground);
return ground;
}

Ourgroundisagiantbox.Itisjustlikeotherboxesthatwe’vebuilt—withone
twist.Insteadofaplain,oldMesh,weuseaPhysijs.BoxMeshhere.Meshesfromthe
Physijscodecollectionarejustlikeregularmeshes,exceptthattheycanalso
behavelikereal,physicalobjects—theyfalldownandbounceoffofeachother.
WhencreatingaPhysijsmesh,wecanpassathirdargumentinadditiontothe
geometryandmaterial.Thatthirdargumentistheobject’smass,whichletsus
makethingsveryheavyorverylight.Inthiscase,wesetthemasstoaspecial
number:0.The0meansthattheshapenevermoves.Ifwedidn’tsetthe
ground’smassto0,thegroundwouldfalldownlikeanythingelse!
Unlikeregularmeshes,thedifferentshapeshavedifferentphysicalmeshes.The
listincludesPhysijs.BoxMesh,Physijs.CylinderMesh,Physijs.ConeMesh,
Physijs.PlaneMesh,Physijs.SphereMesh,andforallothershapes,Physijs.ConvexMesh.
Oncethisfunctionisdefined,weuncommentthecalltoaddGround()inourcode
outline.
» var ground = addGround();
//var avatar = addAvatar();
//var scoreboard = addScoreboard();


Ifeverythingisworking,weshouldseegreengroundwithblueskyinthe
backgroundasshowninthefigure.



BuildaSimpleAvatar
In3Dprogramming,youcanmakesimplegraphicsintwoways.We’lluseboth
inthisgame—onekindforthePurpleFruitMonsterandtheotherkindforthe
fruit.ThesimplegraphictechniquethatweuseforthePurpleFruitMonsteris
calledasprite.
IntheaddAvatar()function,wecreateaninvisible,physics-enabledboxmesh,
thenweaddthespritetotheboxmesh.AddthisfunctionbelowtheaddGround()
function.
function addAvatar() {
var shape = new THREE.CubeGeometry(100, 100, 1);
var cover = new THREE.MeshBasicMaterial({visible: false});
var avatar = new Physijs.BoxMesh(shape, cover, 1);
scene.add(avatar);
var image = new THREE.TextureLoader().load("imagesmonster.png");
var material = new THREE.SpriteMaterial({map: image});
var sprite = new THREE.Sprite(material);
sprite.scale.set(100, 100, 1);
avatar.add(sprite);
avatar.setLinearFactor(new THREE.Vector3(1, 1, 0));
avatar.setAngularFactor(new THREE.Vector3(0, 0, 0));
return avatar;
}

Spritesaregraphicsthatalwaysfacethecamera,whichisexactlywhatwewant
our2Davatartodointhisgame.Spritesaresuper-efficientingraphicscode.
Anytimewecanusethem,wemakeitmucheasierforthecomputertodo
everythingitneedstodotokeepthegamerunningsmoothly.
Spritesstartastiny1by1thingsinascene.Toseethissprite,wescaleitby100
intheXandYdirections—westretchitintheleft/rightandup/downdirections.
TheboxmeshatthebeginningofaddAvatar()isdoingalltheworkoffalling



down,collidingwithfruit,andcollidingwiththeground.Wegiveitasmall
massof1soit’llbeeasytopushwiththecontrolsthatwe’lladdinabit.It’s
invisiblebecausewesetvisible: falseinitsmaterial,butit’sstillthere.Weaddthe
spritetotheboxmeshsoweknowwheretheavataris.
ThelastthingwedoinaddAvatar()istosettheangularandlinear“factors.”
Thesefactorssayhowmuchanobjectcanrotateormoveincertaindirections.
Bysettingtheangularfactortoall0s,we’resayingthatouravatarcannotrotate
inanydirection.Evenifitbouncesoffofspinningfruit,theavatarwillalways
staystraightupanddown.Bysettingthelinearfactortotwo1sanda0,we’re
sayingthattheavatarcanmoveintheXandYdirections,butnottheZ
direction.Inotherwords,we’retellingour3Dcodethateventhoughwe’re
creatingathree-dimensionalshape,itwillonlymoveintwodimensions.
MovebackuptothecodeoutlineanduncommenttheaddAvatarcall.
var ground = addGround();
» var avatar = addAvatar();
//var scoreboard = addScoreboard();

Withthat,wehaveaPurpleFruitMonsteravatar…that’sstuckintheground.

ResettingthePosition
WecouldhavepositionedtheavatarinaddAvatar(),butwejustaddedittothe
scene.Instead,we’llcreateaseparatefunctiontosettheposition.Whyusea
separatefunction?Sowecanre-useit!


WhenwetalkedaboutfunctionsinChapter5,Functions:UseandUseAgain,
wesaidthatsomefunctionstellpartofastory.Thefunctionsinourcodeoutline
dothat—theytellthestoryofsettingupthegame.

Anotherkindoffunctionisonethatgetscalledoverandoveragain.Let’screate
oneofthosefunctionsthatcanstart—orrestart—thegamebymovingtheavatar
toitsstartposition.AddthefollowingaftertheaddAvatar()function:
function reset() {
avatar.__dirtyPosition = true;
avatar.position.set(-0.6*w, 200, 0);
avatar.setLinearVelocity(new THREE.Vector3(0, 250, 0));
}

Thenameofthe__dirtyPositionpropertyissomethingofaprogrammer’sjoke.
We’remakingamesshere,sowesaythatit’s“dirty.”What’sthemess?Since
theavatarisphysics-enabled,wenormallycouldn’tchangeitsposition.We
couldpushittoanewlocation,butwe’renotallowedtoinstantlymoveitfrom
oneplacetoanother.Buttoresetthegame,weneedtodojustthat—
immediatelychangetheavatar’spositionbacktostart.Setting__dirtyPositionto
trueletsusdoit.


dirtyStartswithTwoUnderscores

BesuretoaddtwounderscoresbeforedirtyPosition.
It’snot_dirtyPosition.Thesettingis__dirtyPosition.If
youuseonlyoneunderscore,therewillbenoerrors,
butthemovementcontrolswon’twork.

Wemovetheavatar60percentofthewaytotheleft:-0.6timesthedistancefrom
thecentertotheleftedgeofthewindow.Wealsomoveit200abovetheground.
Finallywesetthespeed—thevelocity—oftheavatar.Weuseavectortostartit



withaspeedof250straightupintheYdirection.
Addacalltoreset()justbelowthecodeoutline.
var ground = addGround();
var avatar = addAvatar();
//var scoreboard = addScoreboard();
» reset();

Thatshouldleaveouravatarhoveringabovetheground,totheleftofthescreen.

Beforeaddingcontrolstomovetheavatar,wehavetotellourphysicsengineto
activelysimulategravityandcollisions.

ActivelySimulatePhysics
AswedidinChapter13,Project:PhasesoftheMoon,weputourgamecode—
ourphysicssimulation—insideafunctionnamedgameStep().Additjustbelow
thereset()function.
function gameStep() {
scene.simulate();
setTimeout(gameStep, 1000/30);
}
gameStep();

Don’tforgettocallthegameStep()immediatelyafterthefunctiondefinition.
Oncethat’scoded,ouravatarshouldstartalittleaboveground,jumpupabit,
thenfallbackdowntotheground.We’renowsimulatingreal-worldphysicsin


ourgame!
ThesetTimeout()insidegameStep()callsgameStep()afterwaitingfor1000/30
milliseconds—roughly30milliseconds.ThatwillaskthePhysijscodetoupdate

thepositionsofeverythinginthesceneevery30milliseconds.Thatsoundslikea
lot,butit’sanicebalance.It’snotsooftenthatcomputerswillstartrunning
slow.It’softenenoughsothattheupdateslooksmoothwhenanimated.
Next,let’saddsomecontrolstomovethePurpleFruitMonsterabout.

MovementControls
Tocontroltheavatar,weusethekeydowneventlistenerthatwesawinearlier
chapters.Addthefollowingcodebelowtheanimatefunction:
document.addEventListener("keydown", sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if
if
if
if
if
if

(code
(code
(code
(code
(code
(code

==
==
==
==
==

==

'ArrowLeft') left();
'ArrowRight') right();
'ArrowUp') up();
'ArrowDown') down();
'Space') up();
'KeyR') reset();

}
function
function
function
function

left()
right()
up()
down()

{
{
{
{

move(-100, 0); }
move(100, 0); }
move(0, 250); }
move(0, -50); }


function move(x, y) {
if (x > 0) avatar.scale.x = 1;
if (x < 0) avatar.scale.x = -1;
var dir = new THREE.Vector3(x, y, 0);
avatar.applyCentralImpulse(dir);
}

There’snothingfancywiththesendKeyDown()eventlistener.Theleft(),right(),


up(),anddown()functionsthatgetcalledareprettysimpleaswell.Theycalla
move()functionwithdifferentamountstomoveintheXandYdirections.

Themove()functionisalittleinteresting.Thefirsttwolinessettheavatar’sX
scaleifwe’removingintheXdirection.ThisflipsthePurpleFruitMonster’s
imagetofaceleftorright,dependingonthedirectioninwhichwe’removing.
Thelasttwolinesofmove()pushtheavatarintheproperdirection.First,we
calculatethedirection.Forexample,whentheleftarrowkeyispressed,theleft()
functioniscalled.Theleft()functioncallsmove(-100, 0),whichtellsmovetosetx
to-100andyto0.Thedirvalueisthensettoavectorpointing(-100,0,0),which
is100totheleft.Applyingacentralimpulseinthatdirectionmeansaquick
pushinthecenteroftheavatar.Pushinginthecenterofanobjectiseasierthan
pushinganedge.Sincewe’reprogrammersandwelikeeasy,wepushinthe
center.
Withthat,weshouldbeabletohidethecodeandmovetheavatarup,down,left,
andright.


AddScoring
Tocompletethecodeoutline,wenextaddthescoreboard.Putthisbelowthe

addAvatar()functionandabovethereset()function.
function addScoreboard() {
var scoreboard = new Scoreboard();
scoreboard.score();
scoreboard.help(
"Use arrow keys to move and the space bar to jump. " +
"Don't let the fruit get past you!!!"
);
return scoreboard;
}

ThisissimilartothescoreboardweusedinChapter11,Project:FruitHunt,so
thecodeshouldlookfamiliar.UncommenttheaddScoreboardfunctioninthecode
outline.
var ground = addGround();
var avatar = addAvatar();
» var scoreboard = addScoreboard();
reset();

Youshouldnowseeascoreboardshowing0points.


Gameplay
Atthispoint,we’redonewiththecodeoutlineandwehavethebasicsforasolid
2Dgame.Wehavetheplayingarea,theavatar(includingcontrols),andawayto
keepscore.Tomakethegameinteresting,westillneedtodoabitmorework.
Next,we’lladdgameplay.We’regoingtorolloutsomefruitandchallengethe
playertomaketheavatareatasmuchaspossiblewithouttouchingtheground.

LaunchingFruit

First,tocreatefruit,addafunctionbelowthereset()function.
function makeFruit() {
var shape = new THREE.SphereGeometry(40, 16, 24);
var cover = new THREE.MeshBasicMaterial({visible: false});
var fruit = new Physijs.SphereMesh(shape, cover);
fruit.position.set(w, 40, 0);
scene.add(fruit);
var image = new THREE.TextureLoader().load("imagesfruit.png");
cover = new THREE.MeshBasicMaterial({map: image, transparent: true});
shape = new THREE.PlaneGeometry(80, 80);
var picturePlane = new THREE.Mesh(shape, cover);
fruit.add(picturePlane);
fruit.setAngularFactor(new THREE.Vector3(0, 0, 1));
fruit.setLinearFactor(new THREE.Vector3(1, 1, 0));
fruit.isFruit = true;
return fruit;
}

That’safairamountoftyping,butitshouldbefamiliar.We’recreatingasphere
asaphysics-enabledstand-inforfruit.Aswiththeavatar,wemakethissphere
invisible.Toenablephysicsonthesphere,wecreateaPhysijs.SphereMesh.Before
addingittothescene,wepositionitofftotherightofthewindowandjust
abovetheground.
Forthefruitimage,weusethesecondwayofaddingsimple,2Dgraphics.After
loadingtheimage,wemapitintoabasicmaterialandaddthattoasimpleplane


loadingtheimage,wemapitintoabasicmaterialandaddthattoasimpleplane
geometry.Wecan’tuseaspritehereaswedidwiththeavatarbecausetheimage
willneedtorotateastheballrotates.

Weagainsetangularandlinearfactorssothatthefruitcanonlymoveandrotate
two-dimensionally.Theangularfactoronlysets1fortheZaxis.Thismeansthat
thefruitwillbeabletospinlikethehandsonananalogclock.
Beforereturningthefruitfromthefunction,wesetanisFruitproperty.Thatwill
helpuslaterwhenweneedtodecidewhethertheavatariscollidingwiththe
groundoroneofthesepiecesoffruit.
Tomakesurethatallofthisistypedincorrectly,addacalltomakeFruit()after
thefunctiondefinition.Youshouldseethefruitontheveryrightedgeofthe
screenandthereshouldbenoerrorsintheJavaScriptconsole.Ifeverythingis
OK,removethemakeFruit()call.
Next,weneedtolaunchthefruit.AddthelaunchFruit()functionabovethe
makeFruit()functiondefinition.
function launchFruit() {
var speed = 500 + (10 Math.random() scoreboard.getScore());
var fruit = makeFruit();
fruit.setLinearVelocity(new THREE.Vector3(-speed, 0, 0));
fruit.setAngularVelocity(new THREE.Vector3(0, 0, 10));
}

WestartbymakingfruitwiththemakeFruit()functionwejustwrote.We
calculatethespeedas500plusalittleextra.Thelittleextraisarandomnumber
thatgetsbiggerasthescoregetsbigger.Agameshouldgetharderthelongerthe
playerplays.Wemakeitharderbyrollingthefruitfasterandfaster!
WeapplythespeedwithsetLinearVelocity().Themotionneedstobefromrightto
left,sowesettheXdirectiontothenegativeofthespeedsetting.Last,wegive
thefruitalittlespin.
Westillneedtocallthisfunction.So,belowthelaunchFruit()function,addtwo
calls.



launchFruit();
setInterval(launchFruit, 3*1000);

ThefirstcalltolaunchFruit()launchesasinglefruitrightaway.Next,weuse
setInterval()tokeepcallinglaunchFruit()every3seconds.ThesetTimeout()function
thatwe’vealreadyseencallsafunctiononceafteradelay.ThesetInterval()
functiondoesthesamething,butkeepscallingoverandover.
Withthat,wehavelotsoffruitheadingatthePurpleFruitMonster.Wecaneven
usethekeyboardcontrolstobounceoffthefruit.Next,weneedtokeepscore
whenthathappens.

EatingFruitandKeepingScore
Allthewayatthebottomofourcode—belowthemove()function,addcodeto
sendcollisionstotherestofourcode.
avatar.addEventListener('collision', sendCollision);
function sendCollision(object) {
if (object.isFruit) {
scoreboard.addPoints(10);
avatar.setLinearVelocity(new THREE.Vector3(0, 250, 0));
scene.remove(object);
}
}

Thislooksalotlikethekeydowneventlistenerthatwe’reusingtorespondto
keyboardactions.Insteadofprocessingandsendingkeyboardeventstothe
game,herewesendcollisionevents.
Iftheavatarcollideswithfruit,weadd10pointstothescore,givetheavatara
littlebumpup,andremovethefruitfromthescreen(becausethePurpleFruit
Monsteratethefruit).
Hidethecodeandtryitout!


GameOver
Wehavealltheelementsweneedforthisgameexceptone:awaytolose.Let’s
addcodesothegameendswhenthePurpleFruitMonstertouchestheground.


addcodesothegameendswhenthePurpleFruitMonstertouchestheground.
Whenthegameends,weneedawaytotellvariouspartsofthecodetostop
doingwhatthey’redoing—atleastuntilthegameisreset.We’lluseagameOver
variableforthat.Additabovethecodeoutline.
» var gameOver = false;
var ground = addGround();
var avatar = addAvatar();
var scoreboard = addScoreboard();
reset();

Thegameisnotoverwhenitfirststarts,sowesetgameOvertofalsehere.
Oneofthewaysforthegametoendiswhentheavatarhitsthegroundorwhen
itcollideswiththeground.SointhesendCollision()functionatthebottomofour
code,addasecondcollisioncheckasshown:
avatar.addEventListener('collision', sendCollision);
function sendCollision(object) {
»
if (gameOver) return;
if (object.isFruit) {
scoreboard.addPoints(10);
avatar.setLinearVelocity(new THREE.Vector3(0, 250, 0));
scene.remove(object);
}
if (object == ground) {

gameOver = true;
scoreboard.message(
"Purple Fruit Monster crashed! " +
"Press R to try again."
);
}

»
»
»
»
»
»
»
}

Iftheobjecttheavatariscollidingwithistheground,thenthegameisover.We
alsoupdatethescoreboardwithahelpfulmessage.Andnotethatwe’rereturning
immediatelyfromthefunctionifthegameisover.There’snoreasontocheck
forcollisionswhenthegameisover!


Atthispoint,wehaveawaytosaythatthegameisover,butnothingstops.
Animationstillhappensandfruitstillrolls.Weneedtoteachourcodewhatit
meanswhenthegameisover.
Backupintheanimate()function,addacheckthatreturnsrightaway—before
anythingisanimated—ifthegameisover.
var clock = new THREE.Clock();
function animate() {
»

if (gameOver) return;
requestAnimationFrame(animate);
var t = clock.getElapsedTime();
// Animation code goes here...
renderer.render(scene, camera);
}
animate();

Dothesamethingaboveanimate(),inthelaunchFruit()function.
function launchFruit() {
»
if (gameOver) return;
var speed = 500 + (10 Math.random() scoreboard.getScore());
var fruit = makeFruit();
fruit.setLinearVelocity(new THREE.Vector3(-speed, 0, 0));
fruit.setAngularVelocity(new THREE.Vector3(0, 0, 10));
}

Then,wehavetoteachthereset()functionhowtostartthingsbackup.
function reset() {
avatar.__dirtyPosition = true;
avatar.position.set(-0.6*w, 200, 0);
avatar.setLinearVelocity(new THREE.Vector3(0, 250, 0));
scoreboard.score(0);
scoreboard.message('');
»
»
»
»


var last = scene.children.length - 1;
for (var i=last; i>=0; i--) {
var obj = scene.children[i];


»
»
»
»
»
»
»

if (obj.isFruit) scene.remove(obj);
}
if (gameOver) {
gameOver = false;
animate();
}
}

We’redoingtwothingshere:removingoldfruitandrestartingthegame.
Toremovethefruit,weloopoverallofthe“children”inthescene—allofthe
objectsthatwe’veaddedtothescene.Atanystepintheloop,ifwefindthatthe
objectisapieceoffruit,wetellthescenetoremoveit.Thisway,whenwereset
thegame,nooldfruitisleft.
Notethatwehavetogobackwardwhenremovingthingsfromalistin
JavaScript—otherwiseweriskskippingthingsthatwedidn’tmeantoskip.The
listofthingsinthescenemightbe:
0: Fruit #1

1: Fruit #2
2: Ground

Ifwestartthefor-loopfrom0,thefirsttimethroughthelist,iwouldbe0,and
wewouldremoveFruit#1fromthescene.Then,thelistwouldlooklikethis:
0: Fruit #2
1: Ground

Thenexttimethroughtheloop,iwouldincreaseto1.Checkingobjectnumber1,
wewouldfindtheground.Sincethat’snotfruit,ourcodewouldnotremoveit
fromthescene.Andthenourloopwouldstopsincethere’renomoreitemsinthe
list.
Butwait!WeskippedrightoverFruit#2.Byremovingsomethingatthe
beginningofthelist,weshifteverythingelseinthelistdownbyone.Bythe
timetheloopstartsagain,wehaveskippedanoldfruit.
Wedon’thavethisproblemdoingthereverse.Insteadofstartingwiththefirst


iteminthelistandincreasingtheivariableeachtime,westartwiththelastitem
anddecreaseiaftereachloop.Theexampleinreversewouldstartlikethis:
2: Ground
1: Fruit #2
0: Fruit #1

Wheniis2,wedon’tdoanythingbecausethegroundisnotapieceoffruit.
Wheniis1,weremoveFruit#2,leavingthelistlikethis:
1: Ground
0: Fruit #1

Then,inthenextloop,iwouldbe0andwe’dremoveFruit#1,leavinguswith:

0: Ground

Endingwithjustthegroundandnofruitiswhatwewant.Themoralofthisstory
isthatweneedtobecarefulremovingthingsfromJavaScriptlists.
Happily,theotherthingwedoinsidethereset()functioniseasier.Weset
gameOverbacktofalseandrestarttheanimate()function.Withthat,ourgameis
ready!


Improvements
Congratulations!Youjustwroteanothergamefromscratch.Andit’safunand
challengingone.Youcanstilluseotherwaystoimprovethings.Some
suggestions:
AddthingsthatthePurpleFruitMonsterdoesn’tlike,tomakethescorego
downwhensheeatsthem.Hint:Animagesrotten_banana.pngimageisalso
available!
StopthegameiftoomuchfruitgetspastthePurpleFruitMonster.Hint:
createacheckMissedFruit()functionthat’scalledattheverybeginningof
launchFruit().The‘checkedFruit()‘functionshouldloopthroughallfruitin
thescene,countingthenumberwhoseXpositionistoolow.Ifthatcountis
toohigh,it’sgameover!
Thisisyourcode,somakethegamebetterasonlyyoucan!


TheCodeSoFar
Ifyou’dliketodouble-checkthecodeinthischapter,turntoCode:ThePurple
FruitMonsterGame.ThatcodeincludesthecheckMissedFruit()improvement,but
tryitonyourownfirst.



What’sNext
Thiswasanimpressivegametomake.Intheupcomingchapters,we’llpractice
thephysicsskillsthatwedevelopedhere.We’llalsobuildontheconceptofa
gameStepfunction,whichwasfairlysimpleinthisgame.Butbeforethat,what
highscorecanyougetwithPurpleFruitMonster?
Copyright©2018,ThePragmaticBookshelf.


×