Build a 3D WE­BGL racer – part two

52 Cre­ate a 3D WE­BGL game

Web Designer - - Con­tents -

The fi­nal part of this two-parter sees the fin­ish­ing touches added to this end­less fly­ing 3D game

In this tu­to­rial the game that was started last is­sue is go­ing to be re­sumed and com­pleted, al­low­ing for a stun­ning game that will work across mo­bile de­vices, tablets and desk­top browsers that have sup­port for WE­BGL. All the ground­work has been started, in­clud­ing the visual look of the game. The model is loaded and it’s all ready for the game logic to be put in.

This is go­ing to con­sist of mak­ing ob­ject classes for each of the four coloured track pan­els that will con­trol their move­ment and the en­e­mies within those pan­els, such as spin­ning saw blades, ob­sta­cles and slid­ing block­ades. The 3D mod­els will be linked up with those classes when they are in­stan­ti­ated as game ob­jects. The next part will be to add the logic to con­trol the build of the pan­els, so that they keep com­ing at the player and get faster. The pan­els also need to be up­dated so that they run their re­spec­tive func­tions each frame and when they’ve passed the user the game needs to re­move them, adding new pan­els in the dis­tance.

The fi­nal part will be col­li­sions to en­sure the player has a chal­lenge. The game will be con­trolled by mov­ing the mouse on desk­top, and on mo­bile de­vices the ship will be con­trolled by drag­ging back and forth on the screen. At the end you will need to upload it to a server to test the game, since it needs a server to load the mod­els in.

1. Get­ting started

Open up the ‘start’ folder in your code edi­tor and open the file ‘game_pt1.js’, which is where the code was fin­ished at the end of part one of the tu­to­rial. Scroll to the bot­tom of the doc­u­ment and start to add the code as shown here. This sec­tion of code con­tains the classes for the track pan­els that con­trol their be­hav­ior. func­tion Cyan(obj) { this.obj = obj; this.en­e­mies = []; }

Cyan.pro­to­type.re­set = func­tion () {

//en­e­mies refers to cubes block­ing the way

for (var i = 0; i < this.en­e­mies.length; i++) { let rpos = Math.floor(math.ran­dom() * 6); this.en­e­mies[i].po­si­tion.x = (rpos * 9) - 18;

}

};

2. Coloured pan­els

Each panel is a dif­fer­ent colour, which was cre­ated like that for de­bug­ging, but stuck as a con­cept. Each coloured panel has a se­ries of code that re­sets it and up­dates it every frame. For the Cyan panel it just ran­domises blocks in the way of the ship for it to swerve around. Cyan.pro­to­type.up­date = func­tion () {

this.obj.po­si­tion.z += (SPEED * delta);

}; func­tion Mag(obj) { this.obj = obj; this.en­e­mies = []; this.l_en­e­mies = []; //bars on each side this.r_en­e­mies = []; }

3. Ma­genta panel

The Ma­genta panel has block­ades down each side that shoot out just be­fore the player gets there. These are ran­domised from each side – left and right – and placed into an ar­ray called ‘en­e­mies’, ready for when the player flies past them.

Mag.pro­to­type.re­set = func­tion () {

//set each side back to orginal po­si­tion for (var i = 0; i < m.l_en­e­mies.length; i++) { this.l_en­e­mies[i].po­si­tion.x = -30; this.r_en­e­mies[i].po­si­tion.x = 30;

}

//re­set en­emy se­lec­tion from last time this.en­e­mies = [];

//choose a new ran­dom side for the 4 spots for (var i = 0; i < 4; i++) { let rnd = Math.floor(math.ran­dom() * 2); if (rnd == 1) { this.en­e­mies.push(this.l_en­e­mies[i]); } else { this.en­e­mies.push(this.r_en­e­mies[i]); }

}

};

4. Up­dat­ing the block­ades

As the player gets within a cer­tain dis­tance of the pan­els, the block­ades shoot out from each side, so that the player has to re­act and dodge the pan­els as they fly down the Ma­genta panel on the screen. The ran­dom na­ture means that the player isn’t sure whether they’re com­ing from the left or the right.

Mag.pro­to­type.up­date = func­tion () { this.obj.po­si­tion.z += (SPEED * delta); if (this.obj.po­si­tion.z > -10) { this.en­e­mies[0].up­date();

} if (this.obj.po­si­tion.z > 80) { this.en­e­mies[1].up­date();

} if (this.obj.po­si­tion.z > 170) { this.en­e­mies[2].up­date();

} if (this.obj.po­si­tion.z > 280) { this.en­e­mies[3].up­date(); }};

5. Set­ting up the Orange panel

The Orange panel has cir­cu­lar saw blades on pen­du­lum arms that fly out of the floor. Here these are all re­set so that the arms are fac­ing straight down. Later as the player ap­proaches, the arms will swing around and the player will have to avoid the saw on the end of this arm. func­tion Oj(obj) { this.obj = obj; this.en­e­mies = []; }

Oj.pro­to­type.re­set = func­tion () {

for (var i = 0; i < this.en­e­mies.length; i++) { let rpos = Math.floor(math.ran­dom() * 4); this.en­e­mies[i].po­si­tion.x = (rpos * 10) - 25; this.en­e­mies[i].ro­ta­tion.z = Math.pi;

//180 fac­ing down this.en­e­mies[i].add(this.snd);

}

};

6. Up­dat­ing the arm

The up­date for the Orange panel checks the po­si­tion of the panel and, if it’s close to the player, then the arm is set to ro­tate. All an­i­ma­tion in the pan­els is con­trolled by a ‘delta’ so that if the frame rate slows down or speeds up, then the move­ment stays con­sis­tent since it’s based on time not frames.

Oj.pro­to­type.up­date = func­tion () { this.obj.po­si­tion.z += (SPEED * delta); if (this.obj.po­si­tion.z > -10) { this.en­e­mies[0].ro­ta­tion.z -= 0.05; this.en­e­mies[0].chil­dren[1].ro­ta­tion.z += delta * (Math.pi * 2); }

if (this.obj.po­si­tion.z > 85) { this.en­e­mies[1].ro­ta­tion.z -= 0.05; this.en­e­mies[1].chil­dren[1].ro­ta­tion.z += delta * (Math.pi * 2);

} if (this.obj.po­si­tion.z > 175) { this.en­e­mies[2].ro­ta­tion.z -= 0.05; this.en­e­mies[2].chil­dren[1].ro­ta­tion.z += delta * (Math.pi * 2);

} };

7. A slice of lime

The Lime panel is set up now and this is one of the sim­plest track pan­els since there is noth­ing to hin­der the player on this. The ‘re­set’ func­tion is still present be­cause every panel that gets called onto the game gets the ‘re­set’ func­tion called, so even though it’s empty it stops the code from break­ing. func­tion Lime(obj) { this.obj = obj; this.tubes; this.init(); }

Lime.pro­to­type.init = func­tion () {

this.tubes = this.obj. geto­b­ject­by­name(“tubes”, true);

};

Lime.pro­to­type.re­set = func­tion () {}

8. Juic­ing the lime

The Lime pan­els up­date re­ally just con­trols the an­i­ma­tion of the tubes ro­tat­ing to give the scene some move­ment as the player flies through this panel. This is also al­ways the start­ing panel for every game that will be played, and so has a grand­stand feel to it.

Lime.pro­to­type.up­date = func­tion () { this.obj.po­si­tion.z += (SPEED * delta); this.tubes.ro­ta­tion.z += 0.01;

};

//re­turns ran­dom num­ber within a range func­tion ge­trand(min­val, max­val) { re­turn min­val + (Math.ran­dom() * (max­val - min­val)); }

9. Link­ing up the classes

Now it’s time to link up the classes with the ac­tual mod­els. In the ‘init’ func­tion is a sec­tion that loads the pan­els, where you will find a com­ment ‘PANEL SETUP TO DO’. The next code goes here. Re­move the line ‘scene. add(dae);’ since this is no longer needed. var x = dae.geto­b­ject­by­name(“cyan”, true); c = new Cyan(x);

//push the en­emy blocks into Cyan’s en­e­mies ar­ray - doesn’t seem to work from in­side the Cyan ob­ject! x.tra­verse(func­tion (child) { if (child in­stanceof THREE.MESH && child. par­ent.name == “en­emy”) { c.en­e­mies.push(child.par­ent);

}

}); in­ac­tive.push(c);

10. Ma­genta mod­els

Next up is the Ma­genta model. Again the class is in­stan­ti­ated and each of the mod­els on the left-hand side that will slide out are placed in an ar­ray name ‘l_en­e­mies’ for the left side. Each en­emy is given its own up­date func­tion to an­i­mate it at the ap­pro­pri­ate time. x = dae.geto­b­ject­by­name(“mag”, true); m = new Mag(x); x.tra­verse(func­tion (child) { if (child in­stanceof THREE.MESH && child. par­ent.name == “en­e­myl”) { m.l_en­e­mies.push(child.par­ent); child.par­ent.up­date = func­tion () { if (this.po­si­tion.x < 5) { this.po­si­tion.x += (120 * delta);

} } }

11. To the right

The same as the left-hand side of the Ma­genta panel, all of the en­e­mies on the right are stored in their own ar­ray and given an up­date func­tion. All of the main coloured pan­els are stored in an ar­ray called ‘in­ac­tive’ when they are placed on screen they are taken out of here, that way

it’s easy to keep track of the pan­els in the game and those wait­ing to be placed. if (child in­stanceof THREE.MESH && child. par­ent.name == “en­e­myr”) { m.r_en­e­mies.push(child.par­ent); child.par­ent.up­date = func­tion () { if (this.po­si­tion.x > -5) { this.po­si­tion.x -= (120 * delta);

} } }

}); in­ac­tive.push(m);

12. Orange seg­ments

The Orange panel is set up with the en­e­mies be­ing lo­cated in here. Those mod­els are all named ‘stick’ as it’s the arm that the saws swing on. These are passed into a new in­stan­ti­ated ob­ject so that they can be used in the game as a com­plete panel. x = dae.geto­b­ject­by­name(“oj”, true); o = new Oj(x); o.en­e­mies = []; x.tra­verse(func­tion (child) { if (child in­stanceof THREE.MESH && child. par­ent.name == “stick”) { o.en­e­mies.push(child.par­ent);

} }); in­ac­tive.push(o);

13. Last panel in the lime­light

The fi­nal panel to be set up is the Lime panel and the new in­stan­ti­ated ob­ject is cre­ated. All pan­els are now stored in the ‘in­ac­tive’ ar­ray. This means that pan­els in this ar­ray can be se­lected and placed in the scene, but re­sources are not wasted on an­i­mat­ing their parts.

14. Plac­ing pan­els

To put pan­els into the scene a new func­tion called ‘build­pan­els’ will be cre­ated. Place this code af­ter the clos­ing bracket of the ‘ren­der’ func­tion. If the ‘ac­tive’ ar­ray is empty then the Lime panel is added as the first panel. This gets re­set and po­si­tioned in the scene. func­tion build­pan­els() {

//first build - add the lime panel if (ac­tive.length == 0) { for (i = 0; i < in­ac­tive.length; i++) { if (in­ac­tive[i].obj.name == “Lime”) { var pln = in­ac­tive[i]; ac­tive.push(pln); scene.add(pln.obj); pln.re­set(); pln.obj.po­si­tion.z = 10;

15. Ran­dom pan­els

Once the first panel is placed in the scene, other pan­els can placed into the scene. These are ran­domly se­lected, so they are added here us­ing a ran­dom num­ber to se­lect one of the re­main­ing pan­els from the ‘in­ac­tive’ ar­ray.

16. Up­ping the speed

The new panel is re­set and po­si­tioned di­rectly be­hind the first panel added in the scene. It’s placed into the ‘ac­tive’ ar­ray and the game counter is in­creased. This is used to in­crease the speed in the game. For every two pan­els that get added the speed goes up slightly. mdl.re­set();

mdl.obj.po­si­tion.z = ac­tive[0].obj. po­si­tion.z - 500; scene.add(mdl.obj); ac­tive.push(mdl); in­ac­tive.splice(rndm, 1); pcounter += 1; if (pcounter %= 2) { if (SPEED < 3) {

SPEED += 5;

} } }

17. At the start of the game

Find the ‘be­gin game’ func­tion and you will see a com­ment ‘BUILD PAN­ELS HERE’. This is the start of the game and where new pan­els should be added into the game. All we need to do here is call the ‘build pan­els’ func­tion, so add that line in.

build­pan­els();

18. Up­dat­ing the pan­els

Find a place to add this code out­side of all other func­tions. Here the pan­els are moved every frame with their own up­date func­tion. If the player has passed over them, the pan­els are re­moved from the game, placed in the ‘in­ac­tive’ ar­ray, ready to be ran­domly called back in. func­tion up­datepan­els() {

for (i = ac­tive.length - 1; i >= 0; i--) { var mdl = ac­tive[i]; mdl.up­date(); if (mdl.obj.po­si­tion.z >= 520) { ac­tive.splice(i, 1); scene.re­move(mdl.obj); in­ac­tive.push(mdl); build­pan­els();

} } }

19. De­tect­ing col­li­sions

A game needs to be able to de­tect if the player hits some­thing. This code fires an in­vis­i­ble ray out of the ship, bring­ing back an ar­ray and dis­tance to all mod­els in front of the player. This can be used to de­tect a col­li­sion with an­other ob­ject. func­tion col­li­sion­test() {

// col­li­sion de­tec­tion - fir­ing 2 rays for each side of the ship for (var i = 0; i < 2; i++) { var orig­in­point = ship.po­si­tion.clone(); orig­in­point.x += (i * 2.6) - 1.3; ray­caster.ray.ori­gin.copy(orig­in­point); let in­ter­sec­tions = ray­caster. in­ter­sec­to­b­jects(scene.chil­dren, true); if (in­ter­sec­tions.length > 0) { var dis­tance = in­ter­sec­tions[0].dis­tance;

20. Hit­ting the spot

The last part of the col­li­sion de­tec­tion is to check if the player is less than 3.5 units to an­other ob­ject, if they are this reg­is­ters as a hit. The vari­able ‘dead’ is changed to ‘true’ to start pro­cess­ing the clean up on the screen at the end of the game.

21. The fi­nal step

In the ‘ren­der’ func­tion there are two com­ments to ‘UP­DATE PAN­ELS HERE’ and ‘COL­LI­SION TEST HERE’. Just call the func­tions as in the code be­low and that will tie all the el­e­ments to­gether in the game so that it works. Make sure you run the game from a server to load the game mod­els over XHR.

//////// UP­DATE PAN­ELS HERE //////// up­datepan­els();

//////// COL­LI­SION TEST HERE //////// col­li­sion­test();

Left The Cyan panel is set up with a num­ber of en­emy blocks, which are ran­domly po­si­tioned on the track panel for the player to avoid

Newspapers in English

Newspapers from UK

© PressReader. All rights reserved.