Showing posts with label physijs. Show all posts
Showing posts with label physijs. Show all posts

Thursday, March 28, 2013

Simple World Coordinates in Three.js

‹prev | My Chain | next›

I may have painted myself into the proverbial corner with my approach to the most recent game in 3D Game Programming for Kids. I am taking readers through the building of a river scene:



Since this is the last game in the book, it is more involved than the rest. As such, I use this as a teaching moment to suggest that readers start to think about code organization. Specifically, I build the game with a series of functions:
  addSunlight(scene);
  var scoreboard = addScoreboard();
  var river = addRiver(scene);
  var raft = addRaft(scene);
  resetGame(raft, river, scoreboard);
Each of these functions has the same abstraction level—either adding or working with Three.js / Physijs objects in the game.

My original thought had been to add in-game items at the same level:
  addSunlight(scene);
  var scoreboard = addScoreboard();
  var river = addRiver(scene);
  var raft = addRaft(scene);
  resetGame(raft, river, scoreboard);
  addInGameItems(scene, river, scoreboard);
As with the other addXXX() functions, I need a reference to the scene so that the in-game items can be added to the scene. I need a reference to the river so that I know where to place the objects. I need access to the scoreboard so that I can add and subtract points as the player's raft runs into these items.

As I found last night, there are problems with this approach. The most glaring of these problems is that these in-game items really ought to be reset whenever the game resets. The second problem is that determining the world coordinates of the bonus items given river frame-of-reference coordinates may be too complex for the book. There are other approaches that don't fit as nicely with the Clean Code outline format that I might explore if I am unable to distill this down to simple code. But hopefully that will not be necessary.

But what am I thinking? Three.js has a localToWorld() method that ought to do just what I want:
  function addFruitPowerUp(location, ground) {
    var mesh = new Physijs.ConvexMesh(
      new THREE.SphereGeometry(10),
      new THREE.MeshPhongMaterial({emissive: 0xbbcc00}),
      0
    );
    mesh.receiveShadow = true;
    var p = ground.localToWorld(
      new THREE.Vector3(location.x, location.y, -20)
    );
    console.log(p);
    mesh.position.copy(p);
    scene.add(mesh);
    
    return mesh;
  }
But instead of getting the expected transform, the console.log() statement is showing:
THREE.Vector3 {x: -45, y: -170, z: -20, ...}
That ends up being below the ground since the ground is tilted. Regardless, the lack of decimal points makes it pretty obvious that no translation to world coordinates actually happened.

If I try this directly in the console, I get:
ground.localToWorld(new f.THREE.Vector3(-45, 0, -170))
THREE.Vector3 {x: -33.77378091216087, y: -166.61132156848907, z: -45.00000000000001, constructor: function, set: function…}
The difference is that, by the time I try it from the console, the scene has been rendered, the side-effect being that the ground's world coordinates have been calculated.

I could call my animate() function before adding bonus items, but it is cleaner to use Three.js's updateMatrixWorld() method directly in my current function:
  function addFruitPowerUp(location, ground) {
    var mesh = new Physijs.ConvexMesh(
      new THREE.SphereGeometry(10),
      new THREE.MeshPhongMaterial({emissive: 0xbbcc00}),
      0
    );
    mesh.receiveShadow = true;
    
    ground.updateMatrixWorld();
    var p = new THREE.Vector3(location.x, location.y, -20);
    ground.localToWorld(p);
    mesh.position.copy(p);
    scene.add(mesh);
    
    return mesh;
  }
With that, I have delicious fruit floating along the river, ready to give players bonus points:



That is actually not too bad. I had to use only three additional lines to get this to work. Unfortunately, it comes at the expense of yet another concept that I need to convey in a chapter already overloaded with concepts. Granted it could end up being a very useful one for budding 3D programmers, but I need to consider this carefully.

There no fewer than 6 dozen concepts that I am not including in 3D Game Programming for Kids. For most, someone with 3D programming experience could legitimately question my abilities as an author for not including them. Converting from local to world coordinates is just one of those. I will take a day to two before deciding if it's worth the risk of overloading readers on new concepts. But at least I know how to do it now.



Day #704

Wednesday, March 20, 2013

Pausing Physics

‹prev | My Chain | next›

Tonight I hope to figure out how to pause Physijs physics. Pausing Three.js animation is simple enough—a guard clause before rendering does the trick:
  var paused = false;
  function animate() {
    requestAnimationFrame(animate);
    if (paused) return;
    // ...
    renderer.render(scene, camera);
  }
  animate();
Something similar seems to work for Physijs:
  // Run physics
  function gameStep() {
    if (!paused) scene.simulate();
    // Update physics 60 times a second so that motion is smooth
    setTimeout(gameStep, 1000/60);
  }
  gameStep();
The CPU's load definitely goes down in response to this, so it would seem as though this helps. The problem is that, upon “un-pause”, the game jumps ahead as if it had been simulating things all along.

I am not quite sure how this happens since the CPU load become negligible when paused. There is a web worker involved in Physijs simulation, but if it were actively working, I would expect the CPU to remain high. My best guess is that Physijs maintains an internal last-updated-physics date and, upon restart it applies physics for the interim. I'm probably completely wrong, but it is a working theory.

And unfortunately, this turns out to be a good working theory. There is a last_simulation_time stored in the worker. Sadly, try as I might, I cannot figure a way to get it to reset or slow down. So I fall back on what I know best: cheating.

I only have one thing moving in the current game, so in addition to pausing animation and not calling scene.simulate(), I also manually stop the raft by setting its mass to zero:
  var mass, velocity;
  document.addEventListener("keydown", function(event) {
    var code = event.which || event.keyCode;
    // ...
    if (code == 80)  { // P
      paused = !paused;
      if (paused) {
        mass = raft.mass;
        velocity = raft.getLinearVelocity();
        raft.mass=0;
      }
      else {
        raft.mass = mass;
        raft.setLinearVelocity(velocity);
      }
    }
  });
It may not be perfect, but it works. By setting the mass to zero, I effectively take it out of Pysijs' simulation. I squirrel the old mass and current velocity away so that, once the game is unpaused, these values can be restored and the game can proceed as desired.

This is not a great solution for a game or simulation with a ton of objects in motion, but it will suffice for my needs.

Day #696

Tuesday, March 19, 2013

Shark Jumping

‹prev | My Chain | next›

I think that I have the big questions answered for the rafting game that I would like to include in 3D Game Programming for Kids. As with everything else in the book, it is a Three.js / Physijs game. The biggest question—how to build the river—was answered by a Physijs-specific feature (height fields). For the most part, I try to stick to “purer” 3D constructs, but this wins for simplicity of implementation and concept. But just because the big question is answered does not mean that everything is easy from here on in. So...

I am going to add obstacles in the river to prevent the player from easily navigating to the end. From last night, I can already add a single ramp:
var ramp = new Physijs.ConvexMesh(
      new THREE.CubeGeometry(100, 100, 300),
      new THREE.MeshBasicMaterial({color: 0xbb0000})
    );
    ramp.rotation.x = Math.PI/4;
    ramp.position.copy(pos);
    ground.add(ramp);
Tonight, I convert that to a function:
 function addSharkJump(pos) {
    var ramp = new Physijs.ConvexMesh(
      new THREE.CubeGeometry(100, 100, 300),
      new THREE.MeshBasicMaterial({color: 0xbb0000})
    );
    ramp.rotation.x = Math.PI/4;
    ramp.position.copy(pos);
    ground.add(ramp);
And randomly place these ramps somewhere in the middle of the river:
  var number_between_20_and_40 = 12, // Math.floor(20 + 20*Math.random()),
      number_between_60_and_80 = Math.floor(60 + 20*Math.random());
  addSharkJump(middle_river_points[number_between_20_and_40]);
  addSharkJump(middle_river_points[number_between_60_and_80]);
I am not 100% sold on the idea that this is the best way for kids to randomize the location of ramps. They will already have seen random numbers and floor at this point, so this is not terrible. Still, I hope to come up with better for the actual book.

Anyhow, I need the obstacle:
  function addSharkJump(pos) {
    // ...
    var shark = new Physijs.ConvexMesh(
      new THREE.CylinderGeometry(1, 10, 20),
      new THREE.MeshBasicMaterial({color: 0x999999})
    );
    shark.position.copy(pos);
    shark.rotation.x = Math.PI/2;
    shark.rotation.z = Math.PI/10;
    shark.position.z = pos.z + 140;
    shark.position.y = pos.y - 200;
    ground.add(shark);
  }
It is not a perfect looking shark, but it is something that kids can easily build:



The last thing I need is a penalty for actually hitting the shark:

  function addSharkJump(pos) {
    // ...
    shark.addEventListener('collision', function() {
      scoreboard.addTime(10);
    });
  }
With that, I have obstacles that penalize the player. This is a good stopping point for tonight. Up tomorrow: power-up items that decrease the timer.


Day #695

Monday, March 18, 2013

Rex Kwan Do Physics

‹prev | My Chain | next›

With the end of the month fast approaching and 3D Game Programming for Kids deadlines looming, it is time to make smaller offerings to the gods of the chain so that I can focus on book writing. There still remain a few outstanding questions over approach to the last game in the book. Hopefully I can answer them in the next few days.

Tonight, I start with how to penalize a player that somehow jumps the banks of the river:



I had hoped to make the friction of the “grass” so high that the raft would come to an immediate stop. It turns out to be tricky to get the numbers just right so that the raft has no friction on the river water, but lots of friction on the grass. Also, if the player times it just right, it is possible to build enough speed to jump the entire length of the river.

So instead of playing a losing numbers game, I add an invisible lid to the game:
  var lid = new Physijs.ConvexMesh(
    new THREE.CubeGeometry(size, size, 10),
    new THREE.MeshBasicMaterial({visible:false})
  );
  ground.add(lid);
  scene.add(ground);
With that, a player that happens to bounce out gets forced right back down.

The other thing that I would like to do tonight it to add ramps to the river so that players can do sweet jumps. I am using Physijs height fields for the ground depression that forms the river. While I am building that, I collection the middle points of the river:
  var shape = new THREE.PlaneGeometry(size, size, faces, faces);
  var cover = Physijs.createMaterial(new THREE.MeshPhongMaterial(), 1, 0.1);

  // Doing vertices here, which is faces+1
  var row_size = faces+1;
  var middle_river_points = [];
  for (var i=0; i<row_size; i++) {
    var center = Math.sin(4*Math.PI*i/row_size);
    center = center * 0.05 * faces;
    center = Math.floor(center + faces/2);
    middle_river_points.push(shape.vertices[i*row_size + center]);
    // Make depression here...
  }
With that, I can copy the position of arbitrary points along the river to place ramps:
  var ramp = new Physijs.ConvexMesh(
    new THREE.CubeGeometry(50, 100, 300),
    new THREE.MeshBasicMaterial({color: 0xbb0000})
  );
  ramp.rotation.x = Math.PI/4;
  ramp.position.copy(middle_river_points[10]);
  ground.add(ramp);
Just like that, I have a sweet jump over which I can get like three feet of air:



And, happily, the invisible lid seems high enough that jumping is not affected. In the next couple of days, I need to add obstacles that slow the player down and speed it up. I also need to add an end game. I think I have the hardest questions answered already, but the devil is always in the details. So who knows what adventures tomorrow brings?


Day #694

Sunday, March 17, 2013

Keeping it Simple… By Cheating Physics

‹prev | My Chain | next›

Writing a book for kids has made me acutely aware of the need to limit both code and concepts when writing. Limiting code and concepts is one of those things that I have always unconsciously tried to do, but now I see it has to be a priority—something that I always have to keep in the forefront. In fact, I have gotten quite obsessive about it, to the point that I will completely delete and restart chapters if need be.

In the case of the last chapter in 3D Game Programming for Kids, I have restrained from even starting the chapter for several months because I was unable to limit either code or concepts with the various approaches that I was trying. That was until Chandler Prall happened to mention Physijs height fields in response to one of my failed attempts at building a river.

After mucking with height fields for a few nights, I have a very satisfactory looking river:



Even better, I made that with 40 lines of code (river trench, water, shading and shadows). Best of all, the concept of a height field is ridiculously simple—make parts of it lower than the rest.

I am still left with the challenge of explaining the nice sine curve that is the river, but that is not hopeless. I need only state that sines and cosine make winding graphs—without actually mentioning their geometric origin. Or I could opt for an even simpler zig-zag river. Regardless, I think that I am ready to proceed with the game.

For the game, I need to solve three more (hopefully) smaller issues. The first is that the camera needs to point well in front of the player's raft so that the player can see what is coming next. Second, the river has to push the raft downstream. Last, I need a way to keep the raft right-side-up—the controls get tricky when the raft flips.

Positioning the camera turns out to be trickier than I had expected. I am using an older version of Three.js (r52) in which the Vector3 class does not support the add(vector) method. Back then, add() added two vectors together and set the current vector to the result. More recent versions of Three.js have made the switch to add(v) producing a new vector from the sums of the current object and v. I am stuck with the old addSelf():
  camera.lookAt(
    donut.position.addSelf(new THREE.Vector3(0.67*height, 0, 0))
  );
The value of 0.67*height (67% the height of the viewport) was chosen through trial and error after positioning the camera with:
  function updateCamera() {
    camera.position.set(
      donut.position.x + 0.75 * height,
      0.1*height,
      donut.position.z
    );
  }
  updateCamera();
This gives the player a reasonable view of what is coming downstream of the raft's current position:



The original idea that I have for the current is to make the water zero friction (the zero in the Phsyijs.createMaterial function):
  var water = new Physijs.ConvexMesh(
    new THREE.CubeGeometry(size, size, 10),
    Physijs.createMaterial(
      new THREE.MeshBasicMaterial({color: 0x0000bb}),
      0,
      0.9
    ),
    0
  );
And then apply a force downstream whenever there is a collision:
  water.addEventListener('collision', function(event) {
    donut.applyCentralForce(
      new THREE.Vector3(1e7, 0, 1e7)
    );
  });
This does not quite work, however. The raft is pushed into the river bank, bounces backward, and eventually falls off the edge of the “world”. I need the initial push to be off to the right a bit. I also need subsequent water force to continue pushing as long as the raft and river are in contact.

The initial motion is easy enough:
  donut.setLinearVelocity(
    new THREE.Vector3(50, 0, -10)
  );
As a side-note, I really need to stop calling this a “donut”. Anyhow...

Whether or not an object is currently colliding with another object is not an easy thing to do in Physijs. In fact, I need to reach under the covers to ask how many objects are currently “touching”:
  setInterval(function() {  
    if (water._physijs.touches.length > 0) {
      donut.applyCentralForce(
        new THREE.Vector3(1e6, 0, 10)
      );
    }
  }, 1000);
That works—the donut/raft is pushed downstream—but it probably violates my concept rule.

It is probably easier to fake current by tilting the ground ever so slightly:
  ground.rotation.y = 0.1;
That involves motion down an incline plane, which is fun for every first year physics student. But I would not need to explain the trigonometry behind the forces. Rather, I can state that things slide down a ramp, which is a concept that kids understand.

Last up, I need to stop the raft from wobbling when to bumps into the water or the sides. Happily, by this point in the book, readers are well familiar with setAngularFactor(). This Physijs method restricts or limits rotational motion around one or all axes. In this case, the easiest thing to do is to prevent rotation entirely:
  var donut = new Physijs.ConvexMesh(shape, cover);
  donut.rotation.x = -Math.PI/2;
  donut.position.set(-2500, 200, 0);
  scene.add(donut);
  donut.castShadow = true;
  donut.setLinearVelocity(
    new THREE.Vector3(50, 0, -10)
  );
  donut.setAngularFactor(new THREE.Vector3(0, 0, 0));
It is a little odd to see the raft/donut bounce completely level, but the simplification in game play makes this a good option.

With that, I can navigate to the end of the river (and the world):



Clearly, the end of the world can use some gussying up. I also need to add a timer and some river obstacles. I think I have a handle on most of that. Hopefully this means that I can finish off this game tomorrow.


Day #693

Saturday, March 16, 2013

Drawing Patterns in Physijs Height Fields

‹prev | My Chain | next›


The Physijs HeightField with which I have been playing for the past few days seems quite promising. It may be too complicated to include in 3D Game Programming for Kids. Still, the landscapes that it makes are quite nice:



Today, I would like to see how I might changed those parallel “rivers” into a single, winding river. I do not believe that it will be easy. Height fields work on the vertices of shapes. If I have a 900×900 plane that I divide into a 3×3 grid, then I have 9 faces (300×300 each) and 16 vertices:



To make a winding depression in that, I would want vertices 1, 6, 9 and 14 lower than the rest. Let's see how that looks in Three.js.

A Three.js plane is constructed with the width and height as the first two constructor parameters. The second two parameters are the number of faces in each dimension. To replicate the above grid, I want:
  var shape = new THREE.PlaneGeometry(900, 900, 3, 3);
Then, to lower grid points 1, 6, 9, and 14, I do the following:
  var shape = new THREE.PlaneGeometry(900, 900, 3, 3);
  var cover = new THREE.MeshPhongMaterial();
  cover.emissive.setRGB(0.1, 0.6, 0.1);
  cover.specular.setRGB(0.2, 0.2, 0.2);

  shape.vertices[1].z = -100;
  shape.vertices[6].z = -100;
  shape.vertices[9].z = -100;
  shape.vertices[14].z = -100;
  shape.computeFaceNormals();
  shape.computeVertexNormals();

  var ground = new Physijs.HeightfieldMesh(
    shape, cover, 0
  );
I then insert a water plane below the surface of the ground, but above the -100 depressions. The result is:



As proof of concepts go, that is actually not too bad. Clearly it is not a winding river. Height fields do not join depressions quite as ruthlessly as I might like. Still, I got the vertices right.

What I need is more faces to make a smoother transition from vertex to vertex. So I swap back to a large plane with 100 faces in both dimensions:
var size = 5000,
      faces = 100;
  var shape = new THREE.PlaneGeometry(size, size, faces, faces);
Since vertices is a one dimensional array, I need to move through it one row at a time. That means a for-loop incrementing the index variable from 0 to the number of vertices in a row:
  // Doing vertices here, which is faces+1
  var row_size = faces+1;
  for (var i=0; i<row_size; i++) {
    // manipulate the height map here...
  }
Now, I need to manipulate the nature of the winding river. For the frequency that the river undulates, I opt for 2 full sine waves, 4*Math.PI. For the amplitude, I use 0.05 times the number of faces (5% of the total faces in each row). Last, I have to offset the winding to the middle of each row. This all looks like:

  var row_size = faces+1;
  for (var i=0; i<row_size; i++) {
    var j = Math.sin(4*Math.PI*i/row_size);
    j = j * 0.05 * faces;
    j = Math.floor(j + faces/2);
    // manipulate height map here...
  }
With that, all that is left is to create the depression for the river. I do this on the vertex that I found above and two vertices on either side for better effect:
  var row_size = faces+1;
  for (var i=0; i<row_size; i++) {
    var j = Math.sin(4*Math.PI*i/row_size);
    j = j * 0.05 * faces;
    j = Math.floor(j + faces/2);
    shape.vertices[i*(row_size) + j-2].z = -50;
    shape.vertices[i*(row_size) + j-1].z = -90;
    shape.vertices[i*(row_size) + j].z   = -100;
    shape.vertices[i*(row_size) + j+1].z = -90;
    shape.vertices[i*(row_size) + j+2].z = -50;
  }
  shape.computeFaceNormals();
  shape.computeVertexNormals();
I also remember to recompute the normals so that Three.js can do the shading right. With that, I have a nice, winding river:



Best of all, if I move my raft into the river (and point the camera at it), then it interacts well with both the river and the height field ground:



The banks of the river are a little blocky, but, in a 3D-computer-kind-of-way, that is not horrible.

I am a little worried about explaining that for-loop to kids. The mapping of the one dimensional array into two dimensions seems tricky. Especially since the two dimensions are array space that happen to map into coordinate space. That said, this is far easier than some of the river segment solutions that I had previously explored. And it looks nicer. Definitely worth exploring a bit more.

(live code of the river)


Day #692

Friday, March 15, 2013

Playing Nice with Physijs Height Fields

‹prev | My Chain | next›

Up today I would like build on my Physijs height field work from yesterday. I still do not know if they will make a good fit for 3D Game Programming for Kids, but they are interesting enough to warrant another play date.

I have a simple, wavy height field for my donut / raft to play in:



I would like to see if I can add a material that is a bit more realistic. I would also like to see if I can add Physijs material in between the waves so that the bottom of the waves are covered. I start with the latter because I am curious to see if I can “dig” out a trench in a height field mesh to make a river—a key component in the last game that I want in the book.

So I add water:
  var water = new Physijs.ConvexMesh(
    new THREE.PlaneGeometry(1000, 1000),
    new THREE.MeshBasicMaterial({color: 0x0000bb})
  );
  water.rotation.x = -Math.PI/2;
  water.position.y = -100;
  water.receiveShadow = true;
  scene.add(water);
And it just works:



The other thing that I hope to understand tonight is why my hills are a little dull. On the one hand, they are grassy hills—it is not as if they should be very shiny. On the other hand, there should be a little shading given that the material used is a MeshPhongMaterial:
  var shape = new THREE.PlaneGeometry(1000, 1000, 100, 100);
  var cover = new THREE.MeshPhongMaterial();
  cover.emissive.setRGB(0.1, 0.6, 0.1);
  cover.specular.setRGB(0.2, 0.2, 0.2);
  // ...
  var ground = new Physijs.HeightfieldMesh(
    shape, cover, 0
  );
My problem turns out to be in the elided code which sets the height field's height:
  for (var i=0; i<shape.vertices.length; i++) {
    var vertex = shape.vertices[i];
    vertex.z = 25 * Math.cos(vertex.x/40);
  }
After mucking with the vertices height like this, I need to tell Three.js to recompute normals:
  for (var i=0; i<shape.vertices.length; i++) {
    var vertex = shape.vertices[i];
    vertex.z = 25 * Math.cos(vertex.x/40);
  }
  shape.computeFaceNormals();
  shape.computeVertexNormals();
And, just like that, I have some decent shading on my hills:



These height maps definitely seem promising. Up tomorrow, I will see if I can figure out how to dig winding paths through them. The nature of the shape vertices makes this seem like a non-trivial problem. If I can solve it, then I just might have the setting for the river rafting game in the last chapter.


Day #691

Thursday, March 14, 2013

Physijs HeightField

‹prev | My Chain | next›

According to my little tag cloud, I have written 48 posts on Physijs. In all those posts and all the research necessary for them, I somehow managed to never come across the HeightField class in the list of supported objects. Thankfully Chandler Prall made mention of them, so now I at least know of their existence. I have absolutely no idea if they will be of any use to me in 3D Game Programming for Kids. There is no way to know unless I take at least a little time to play.

Before I get started, it is worth noting that there is a nifty sample page for HeightField on the physijs site.

I have a simple lights an materials chapter in the book that takes the reader though creating a donut and shadow (and eventually animation):
  var shape = new THREE.TorusGeometry(100, 50, 8, 20);
  var cover = new THREE.MeshPhongMaterial();
  cover.emissive.setRGB(0.8, 0.1, 0.1);
  cover.specular.setRGB(0.9, 0.9, 0.9);
  var donut = new THREE.Mesh(shape, cover);
  scene.add(donut);
  donut.castShadow = true;

  var shape = new THREE.PlaneGeometry(1000, 1000);
  var cover = new THREE.MeshBasicMaterial();
  var ground = new THREE.Mesh(shape, cover);
  ground.position.set(0, -200, 0);
  ground.rotation.set(-Math.PI/2, 0, 0);
  ground.receiveShadow = true;
  scene.add(ground);
A light source and renderer tweaks result in:



To get started with HeightField, I replace the ground that is created from a THREE.Mesh to a Physijs.HieghtField:
  var shape = new THREE.PlaneGeometry(1000, 1000);
  var cover = new THREE.MeshBasicMaterial();
  var ground = new Physijs.HeightfieldMesh(
    shape, cover, 0
  );
  // ...
After making that change, nothing happens. More precisely, nothing changes on the screen—I still have my donut casting a shadow. At least I have not broken anything.

To get height in my height field, I need to change the z value of the vertices in the ground. To achieve smooth bumps in the height field, I divide up the geometry shape into a 50 by 50 grid. Then I work through each vertex in the resultant shape, setting the z value to the cosine of the x position:
  for (var i=0; i<shape.vertices.length; i++) {
    var vertex = shape.vertices[i];
    vertex.z = 10 * Math.cos(vertex.x);
  }
The result is a pretty cool ripple effect in the ground:



So, of course I make that into rolling hills and add controls to the donut to speed about:



Good times.

These height field objects are definitely pretty cool. I am still not sure if I have a good use for them in the book, but I will let the idea percolate for a while. Hopefully I can come up with something...


Day #690

Wednesday, March 13, 2013

Adpaters to Chain Adding of Three.js Objects

‹prev | My Chain | next›

I return tonight to a Three.js problem that has vexed me for several months now. I have what I think is a fairly solid game idea for 3D Game Programming for Kids, but cannot seem to pull it off in a way that makes for a decent narrative. The game has a river rapids setting in which the player has to navigate the river and obstacles to reach the end:



The turns in the river have proven to be the most difficult aspect of this game. Attempts in the past have involved too much geometry or arcane Three.js conversions.

I think it probably best to give up the ghost of this approach. In fact, I may have a better game idea in mind already. But I hate admitting defeat...

Most of my previous attempts have kept the river segments in a global coordinate system. Each subsequent segment then needs some way to know where the previous segment leaves off:
  offset = riverSegment(0);
  offset = riverSegment(Math.PI/8,  offset);
  offset = riverSegment(0,          offset);
  offset = riverSegment(-Math.PI/8, offset);
  // ...
This kinda/sorta works—except when I try to put it into chapter format. Even as the last game in the book, wherein kids and beginners have some pretty good skills, calculating that offset is ugly (see previous posts for the ugly).

But what if I do not use a global coordinate system? What if each segment creates its own frame of reference into which the next segment is placed? That is actually a technique that I try to teach multiple times in the book. Maybe it can work here as well.

In this scenario, I would like to do something like the following when building up the river:
  var river = riverSegment(0).
    add(riverSegment(Math.PI/8)).
    add(riverSegment(-Math.PI/8)).
    add(riverSegment(-Math.PI/8));
For this to work, I would replace the offset calculations at the end of riverSegment():
function riverSegment(rotation) {
  // ...
  return {
    x: Math.cos(rotation) * 1500 + offset.x,
    z: -Math.sin(rotation) * 1500 + offset.z
  };
}
With a frame of reference centered on the end of the river segment:
function riverSegment(rotation) {
  // ...
  var end = new Physijs.ConvexMesh(
    new THREE.PlaneGeometry(1,1),
    new THREE.MeshBasicMaterial()
  );
  end.position.x = length;
  segment.add(end);

  return end; 
}
The add() method in
  var river = riverSegment(0).
    add(riverSegment(Math.PI/8)).
    add(riverSegment(-Math.PI/8)).
    add(riverSegment(-Math.PI/8));
Would come from the end frame-of-reference return value. Brilliant. Except that it will not work.

First, the river variable would be assigned to the return value of that last add(), not the combination of all of those segments. The second problem is that add() in Three.js does not return anything. Of course this second problem causes everything to break—by the time I call add() on the return value of the first add(), I am calling add() on undefined.

This means that I need a proxy object to wrap my Three.js objects and to expose a useful add() method. I start by converting that riverSegment() function to create a RiverSegment object:
function riverSegment(rotation) {
  return new RiverSegment(rotation);
}
Now I can create a the RiverSegment object:
function RiverSegment(rotation) {
  this.rotation = rotation;
  this.init();
}

RiverSegment.prototype.init = function() {
  // Three.js & Physijs initialization here...
  this.mesh = segment;
  this.end = end;
};

RiverSegment.prototype.add = function(segment) {
  this.end.add(segment.mesh);
  return segment;
};
By virtue of that add() method that returns the next object, I can chain add() calls. And this actually works. But is this appropriate for a book for kids and beginners?

Crazy as it might seem... maybe. By this point in the book, I have supplied several frame of reference examples and we will have three chapters of JavaScript object programming under our collection belts. So it is not completely insane to think kids would not be able to keep up.

But really, that is pretty insane. An adapter for the built-in Three.js add() method is not something to expect beginners to appreciate. For my own edification, I am glad to have gotten this to work, but I think it best to rework the game completely.

(a complete mess of demo code)

Day #689

Sunday, March 10, 2013

Converting Three.js Frame of Reference Coordinates

‹prev | My Chain | next›

I think that I have given up on my current approach to a Three.js / Physijs river rafting game. I still hope to come up with something similar as the last game for 3D Game Programming for Kids, but the current approach is proving to be too complex—even for the last game in a beginner's book.

Most of the trouble comes from trying to place river segments:



Excusing the gaps in the river segments, simply placing the segments is a pain. Three.js shapes are positioned from the center of the objects. To account for this, I have to create a new frame of reference for each segment and shift the segment down by half. Once I am done with that, I somehow have to communicate to the next segment the point at which the previous segment stopped.

So I end up defining my river segments at a high-level like this:
  var offset;
  offset = riverSegment(0);
  offset = riverSegment(Math.PI/8,  offset);
  offset = riverSegment(0,          offset);
  offset = riverSegment(-Math.PI/8, offset);
  // ...
That is almost OK, except that the riverSegment() function needs to end with some non-trivial trigonometry:
function riverSegment(rotation, offset) {
  // ...
  return {
    x: Math.cos(rotation) * 1500 + offset.x,
    z: -Math.sin(rotation) * 1500 + offset.z
  };
}
Actually, that is pretty trivial trigonometry and this is a 3D book—it is hard to avoid all mentions and applications of trigonometry in a 3D book. There are other reasons that the current organization of the game is shaping up to be trouble, but it all starts here.

Although, perhaps I can avoid this...

Rather than accumulating world coordinates like this. I believe that I can ask Three.js what the world coordinates of these river segments are. I need to know the world coordinate of the end of each of these segments. So I create vanilla 3D object a segment's length away from the start:
  var end = new THREE.Object3D();
  end.position.set(1500, 0, 0);
  segment.add(end);
I am adding this point into the segment's frame of reference. If the segment is rotated, this point should rotate with it. To get Three.js to tell me the world coordinate, I have to manually call updateMatrixWorld on the segment. This pushes the "end" matrixWorld into the worl frame of reference so that I can grab a 3D point:
  // ...
  segment.updateMatrixWorld();
  var position = end.matrixWorld.multiplyVector3(new THREE.Vector3());
  console.log("%s, %s, %s", position.x, position.y, position.z)
  console.log(" => %s, %s", Math.cos(rotation) * 1500 + offset.x, -Math.sin(rotation) * 1500 + offset.z)
  // ...
The two console.log() statements compare the 3D coordinates of the end point in the world frame of reference. And they turn out to be identical—at least to within a small margin of error:
1500, 0, 0 
 => 1500, 0 

2885.8193359375, 0, -574.025146484375 
 => 2885.81929876693, -574.0251485476347 

4385.8193359375, 0, -574.025146484375 
 => 4385.81929876693, -574.0251485476347 

5771.638671875, 0, -0.000007271766662597656 
 => 5771.63859753386, 0 
So that would work, but I cannot honestly say that updateMatrixWorld() and matrixWorld are any easier to explain to kids than trigonometry would be.


Day #686

Saturday, March 9, 2013

Again with Physijs Plane Meshes

‹prev | My Chain | next›

I solved a minor Physijs mystery last night when I realized that I had to use nothing but ConvexMesh objects when assembling grouped objects. I believe that I now have my very old rafting game almost working condition in the code editor used in 3D Game Programming for Kids.

As an aside, it is surprising how much I have grown accustomed to coding in my fork of Mr Doob's code editor. Between the changes that I made and the latest and greatest editing features in ACE code editor, it is really quite nice. Much better than coding in Emacs and reloading the browser to see my changes. It is hard to believe that this is how I started way back when. I really owe Mr.doob a many debts of thanks between the Three.js library and his code editor (now html editor).

Anyhow, I take some time to rip out my early scoreboard prototype from this game, replacing it with the scoreboard class that I wrote recently to support the book. That is something of a superficial concern. Much more troubling is that I still do not know how to easily code the various river segments in a fashion that is digestible by beginners. The solution that eventually made me set aside this game involved much trigonometry. I still worry about this problem, but before I can tackle it, I still have a bug in the copied game.

Right when the game starts, the raft shoots off to the side:



This turns out to be the same thing that caused me trouble last night—only in an entirely new way. In the game, there are obstacles in the river that cause the game to end:
function addObstacleMarker(water) {
  var marker = new Physijs.PlaneMesh(
    new THREE.PlaneGeometry(1, 1),
    new THREE.MeshBasicMaterial()
  );
  // ...
}
This is merely intended to provide a frame of reference for the actual marker, hence the one-by-one plane. Last night I found that these plane meshes, when combined with convex meshes, wound up with no “physical” impact in the game—players passed right through them.

Today, I find that this plane geometry—even through it is supposed to be 1 pixel by 1 pixel in dimension, is extending out infinitely in physical space, causing the raft to shoot out from the starting line. The solution is simple enough, even if a bit strange, I convert my frame of reference into a Physijs convex mesh:
function addObstacleMarker(water) {
  var marker = new Physijs.ConvexMesh(
    new THREE.PlaneGeometry(1, 1),
    new THREE.MeshBasicMaterial()
  );
  // ...
}
With that, I can again play (and lose) the game without shooting off into dry land:



I call it a night here. Up tomorrow -- I really need to finalize how I want to add these river segments together. And if I cannot, it may be time to punt on this game and chose another for the last chapter in the book.


Day #685

Friday, March 8, 2013

Grouping Convex Meshes

‹prev | My Chain | next›

A while back, I was able to group Physijs objects in my Three.js simulations. I seem to have lost that ability.

I am trying to add a river bank to my simulation. Eventually it will be invisible, but for now I am using a mesh normal material so that I can see it:



I can see it, and I can see my “raft” melting into it.

My river segments are a combination of Physijs planar meshes (serving as a river segment frame of reference), convex meshes (the river “water”) and convex meshes (the banks). Because each segment has two banks, the banks are generated by a function:
  function bank(length, x) {
    var width = 100
      , half = width / 2;

    var bank = new Physijs.ConvexMesh(
      new THREE.CubeGeometry(length*.9, width, 50),
        Physijs.createMaterial(
          new THREE.MeshNormalMaterial(), 0.2, 0.9
        ),
      0
    );
    // ...
    return bank;
  }
I am taking those Physijs meshes and adding them to water mesh, which is added to the river segment:
    // ...
    var segment = new Physijs.PlaneMesh(
      new THREE.PlaneGeometry(10, 10),
      new THREE.Material()
    );

    var water = new Physijs.ConvexMesh(
      new THREE.CubeGeometry(length, 500, 3),
      new THREE.MeshBasicMaterial({color: 0x483D8B})
    );

    segment.add(water);

    water.add(bank(length, 250));
    water.add(bank(length, -250));

    scene.add(segment);
    // ...
In the past, I found it important to add new objects to the grouping object before the main object was added to the scene. If the additional objects were added to the group after the group was already added to the scene, then Physijs would ignore those objects—kinda like it is ignoring the river bank now. Only now I am adding the bank to the segment before the segment is added to the scene and Physijs is still ignoring my bank.

It is possible that this is the result of a new version of Physijs. The previous version was 49 (I think). The current version being used for the games in 3D Game Programming for Kids is 52. Before pursuing the version theory, I start with a sanity check. Instead of adding the back to the river segment, I manually add it to the scene:
    // ...
    var b1 = bank(length, -250);
    b1.rotation.z = Math.PI/2;
    b1.position.set(-250, -750, 0);
    scene.add(b1);

    scene.add(segment);
    // ...
And that works just fine. My river raft now bounces of the bank/wall as desired. That is good to know, but I really need to be able to add the bank to the water in order to place these river segments end-to-end along the entire course.

My next step is to remove the segment and try adding the water directly to the scene without the segment's frame of reference. This actually works, which provides the clue needed to resolve this. It turns out that I cannot use a Physijs.PlaneMesh as the parent object. I convert it over to another convex mesh and it all works fine:
    var segment = new Physijs.ConvexMesh( /* ... */ );
    // ...    
    var water = new Physijs.ConvexMesh( /* ... */ );
    // ...

    segment.add(water);
    water.add(bank(length, -250));
    scene.add(segment);
I can rationalize an explanation for this behavior—mixing and matching two and three dimensional physical meshes does not make much sense. Still, it worked at one point. I probably need to give this a try in the most recent version of Physijs and, if the behavior is still present, add an issue to the project. Happily, I have a solid workaround for the game, so I count that as progress.


Day #684

Thursday, March 7, 2013

Bumps, Not Banks

‹prev | My Chain | next›

It has been a very long time, but I am finally ready to have another go at my river rafting game. When last I played with this game, I had gotten it to a not-quite-working state and was certain to be too complex for inclusion in 3D Game Programming for Kids. It may still prove too complex to be included in the book, but I hope not. It would be a nice 3D game with reasonable physics. If I can pull it off, it may even be worthy of being the last game in the book.

The main problem that I was never able to solve to my satisfaction was joining different river segments. Working with smooth paths is doable in Three.js, but problematic in Physijs. Since I need physics in this game, I stuck with adding straight segments at 45° angles:



The river sides would eventually be invisible (though still with physical presence to keep the player's raft on the river). When the sides are visible however, it is obvious that the sides have a tendency to protrude into the river. This is even more pronounced should two segments be at 90° angles:



After a bit of fiddling, I think it best to drop the idea of making these river side bard banks. Instead, I can make them very short—the kind of thing that a raft can easily jump over if it hits it at too high a rate of speed:
function bank(length, z) {
  var width = 100
    , half = width / 2;

  var bank = new Physijs.BoxMesh(
    new THREE.CubeGeometry(length*.9, 10, width),
      Physijs.createMaterial(
        new THREE.MeshNormalMaterial(), 0.2, 0.9
      ),
    0
  );
  // ...
  return bank;
}
This allows me to avoid some tricky math in the book while at the same time adding a bit more strategy to the game.

Except that the banks no longer work. I had originally added these in older versions of Physijs and Three.js. When I try to play the game now, the player's raft is passing right through the banks as if they are not there. I had solved this a while back, but the solution seems to have changed. Something to work on tomorrow.

Day #683

Saturday, February 23, 2013

Adding Sound to Three.js Games

‹prev | My Chain | next›

One thing that I do not cover in 3D Game Programming for Kids is sound. I am concentrating more on the 3D gaming aspects than overall multimedia experience. Also, I am really trying to teach JavaScript, but don't tell anyone—I'd rather people think game programming is my main goal (better marketing). Anyhow, I would be remiss if I did not at least explore game sound.

The book has the distinct advantage of requiring Chrome. I may try to add Firefox as well, but the bottom line is that I do not have to worry about certain backwater browsers due to the need for WebGL. This means that I do not have to worry my pretty little head over backward compatibility in sound—HTML5 will work just fine.

Of course, I have never played with HTML5 sound before...

I grab a single sound from Freesound. I am not looking for game soundtracks, just ~1 second collision sounds. In case I actually do end up using these, I opt for an attribution-only (all Freedsound files are creative commons) sound from "orginaljun.deviantart.com". I upload it to gamingjs.com as "sounds/donk.mp3".

I will add this sound to the stripped-down version of a recent Three.js game in which arrow controls move a little ball around the screen:



I add the "donk" sound as:
  var audio = document.createElement('audio');
  var source = document.createElement('source');
  source.src = '/sounds/donk.mp3';
  audio.appendChild(source);
  audio.play();
And that does the trick. When the game loads, I get hear the desired "donk".

To hear it when the player runs into the wall, I move the audio.play() call into the collision event (from Physijs) of the player:
  player.addEventListener('collision', function(object) {
    audio.play();
  });
That is all there is to it. Now when the player runs into one of the walls, I hear a surprisingly reassuring "donk" noise.

I add a timeout before adding the sound:
  setTimeout(function() {
    player.addEventListener('collision', function(object) {
      audio.play();
    });
  }, 500);
This prevents a "donk" when the player is first added to the screen.

I must admit that even a simple sound like "donk" does add something to the experience. It might be worthwhile creating a simple library with a few sounds that can be played like: Sounds.donk.play(). I will have to think about that as the book is already getting nearing capacity for concepts. Simple sounds like this seem simple enough, but I need to figure out how or if I would like to support continuous sounds (loop=true) like those that a player might make while walking. That is something for another day.

The sound-enabled simulation.

Day #671

Tuesday, February 12, 2013

Code Extracting for Fun (and less typing for small hands)

‹prev | My Chain | next›

I have a 350 line game candidate for 3D Game Programming for Kids. No kid (and no adult for that matter) is going to want to type in 350 lines of code. That's just crazy. Fortunately, much of the code is already boilerplate from templates in the ICE Code Editor. Some of the remaining code can either be placed in new templates or moved into a library. Tonight, I am going to move as much as possible out into a library to get a better idea of how large the actual code is.

It has been a while, but my fork of Mr Doob's code editor can be run locally as a simple node.js / express.js app:
➜  code-editor git:(gamingjs) ✗ node app
Express server listening on port 3000 in development mode
It would be nice if the order of the libraries did not matter, but, since my mouse library requires both Three.js and Physijs, I need to source them first, then my extracted mouse library:
<body></body>
<script src="/52/Three.js"></script>
<script src="/52/physi.js"></script>
<script src="/mouse.js"></script>
<script src="/52/ChromeFixes.js"></script>
<script>
The /52 libraries are local copies of the various libraries compatible with Three.js r52, which eventually make it up to http://gamingJS.com/ice.

Actually, I am able to move mouse.js ahead of the other two libraries if I add a timeout before monkey patching the built-in addEventListener():
function extendPhysijsAddEventListener() {
  if (typeof(Physijs) == 'undefined') return setTimeout(extendPhysijsAddEventListener, 5);

  var pjs_ael = Physijs.ConvexMesh.prototype.addEventListener;
  Physijs.ConvexMesh.prototype.addEventListener = function(event_name, callback) {
    if (event_name == 'drag') Mouse.addEventListener('drag', this, callback);
    pjs_ael.call(this, event_name, callback);
  };
}
extendPhysijsAddEventListener();
That does not seems particularly robust, so I will likely require readers to stick to proper ordering of the scripts (or add it to the boilerplate code).

With the mouse library extracted, my game is down to... 267 lines. Progress, but not enough. A bit more work eventually gets me down to 250 lines of code, with roughly 30 of it boilerplate. That is about 50 lines of code past my limit for a chapter. It is unfortunate, but not altogether unexpected. The game is moderately complex, after all:


The game includes a player (the red ball), movable ramps, stationary obstacles, screen boundaries, and a goal. On top of that, the recently added multi-level play is just too much. Indeed, if I remove the multi-level code, I get down to 200 lines of code. So it seems that, if I want multi-level games in the book, then I am going to need a separate chapter. I am not sure my editor is going to be happy about that.

The mouse handling code is in pretty ugly shape, but is probably good enough for my needs. So I commit that and put to the 3D Game Programming for Kids site. With that, I do not think there is much left for me in this chapter save to write the actual chapter. If a separate chapter is required, I may as well start on a new scoreboard library, which can be used to keep track of total time elapsed and score in the multi-level game. That is something for tomorrow.


Day #660

Monday, February 11, 2013

Object Literals vs. Function Constructors

‹prev | My Chain | next›

I put proverbial pen to proverbial paper today on the objects chapter in 3D Game Programming for Kids. After the first sentence, I realize that my plans to stick with a pure prototypical introduction to object oriented coding would skip something very important: the new keyword. I have the kids using new as early as the first chapter, so it would seem bad manners not to explain it at all.

In the post-OOP game chapter that I am currently working, I have been making the ramps (in blue) from
Three.js / Physijs objects:


I had been doing this with an object literal describing the prototypical ramp:
  var ramp = {
    startAt: function(location) {
      // constructor things here...
    },
    addMouseHandler: function() { /* ... */ },
    addKeyHandler: function() { /* ... */ },
    // ...
  };
And then I take this prototypical ramp to create real ones:
  var ramp1 = Object.create(ramp);
  ramp1.startAt({
    position: [ /* ... */ ],
    rotation: /* ... */
  });
  scene.add(ramp1.mesh);
Of course, the startAt() method is little more than a constructor, so I can rewrite this as:
  function Ramp(options) { /* ... */ }

  Ramp.prototype.addMouseHandler = function() { /* ... */ };
  Ramp.prototype.addKeyHandler = function() { /* ... */ };
Easy-peasy, except that I now have to explain what that prototype thing is to the poor kids. I am also not sure that I can continue to use object literals quite as freely. Since I had been building the ramp from a single object, I could re-use the concept elsewhere. For instance, I had been creating new ramps by supplying the position and orientation in an object literal:
  var ramp1 = new Ramp({
    position: [0.4 * width * randomPlusMinus(), 0.4 * height * randomPlusMinus()],
    rotation: 2 * Math.PI * Math.random()
  });
  scene.add(ramp1.mesh);
I could even perform that scene.add() inside the constructor if I supply the scene:
  var ramp1 = new Ramp({
    position: [0.4 * width * randomPlusMinus(), 0.4 * height * randomPlusMinus()],
    rotation: 2 * Math.PI * Math.random(),
    scene: scene
  });
The only trouble is that, with my switch to function constructors, I would now be required to introduce both object literal and function constructed objects. Bother.

I specifically avoided object literals when I introduced arrays as I thought they might be too much, too soon. Now they seem too much, even later. If I really want to explain the new keyword that I have the kids use throughout the book, I think it may be best to avoid object literals, if at all possible. Then again, how can I not mention something that fundamental at all?

I think it best to step away from this for a while. I have two working implementations for this game. I will take some time to write the chapter to see which works better with the narrative that I choose. One thing is for certain, as much as I love JavaScript, it does not make this an easy concept to introduce.


Day #659

Sunday, February 10, 2013

Mr Creosote's Simple Multi-Level Games

‹prev | My Chain | next›

I think I have a good handle on how to introduce the basics of object oriented JavaScript programming in 3D Game Programming for Kids. The randomization that I introduced last night seems a little much—at least for the relatively simple game chapters that I am including. At the risk of adding one wafer-thin mint too many, tonight I am going to explore adding multiple levels to my current game.

While reviewing the various chapters and games as the book neared beta, the lack of multi-level or multi-room games seemed like a potential problem. It is a little hard to introduce something like that in the short bursts that I am using, but at the same time, I can imagine kids getting frustrated with working through a 300 page book and not seeing at least a simple example. So even though this is liable to cause poor Mr Creosote to explode, I think it worth exploring.

I continue to work with my Three.js / Physijs puzzle game, the object of which is to place ramps in the right places in order to reach the goal at the top of the screen:


The dark grey (or is it gray? I can never remember) block in the middle of the screen is a randomly placed obstacle. Unlike the ramps, it is immobile and its sole purpose is to get in the way. For a second level, I plan to add a second such obstacle. For the third level, I will add "stalactites" at the top of the screen to make it harder still to reach the goal.

I had originally thought that the various levels could be defined in a JavaScript object. This game does follow the introduction to JavaScript objects chapter, after all. Since the levels are supposed to progress in order, an array would probably make more sense. And then I realize that either way, I am thinking about data structures for what should be a simple game add-on. A hash of a list of level objects? A list of levels each with a list of level objects? It seems almost trivial — it would just be JSON — but not to a kid.

Let's see if I can do it without JSON or data structures. The current obstacle is added as:
  var obstacle = new Physijs.ConvexMesh(
    new THREE.CubeGeometry(height * 0.5, height * 0.1, 10),
    Physijs.createMaterial(
      new THREE.MeshBasicMaterial({color:0x333333}), 0.2, 1.0
    ),
    0
  );
  obstacle.position.y = 0.3 * height * randomPlusMinus();
  scene.add(obstacle);
That will end up being a lot of typing for three levels with up to 4 obstacles in each. So I define a factory function to build either platform or stalactite obstacles:
  function buildObstacle(shape_name, x, y) {
    var platform_shape = new THREE.CubeGeometry(height/2, height/10, 10),
        stalactite_shape = new THREE.CylinderGeometry(50, 2, height/3);
     
    var shape;
    if (shape_name == 'platform') {
      shape = platform_shape;
    } else {
      shape = stalactite_shape;
    }

    var material = Physijs.createMaterial(
      new THREE.MeshBasicMaterial({color:0x333333}), 0.2, 1.0
    );

    var obstacle = new Physijs.ConvexMesh(shape, material, 0);
    obstacle.position.set(x, y, 0);
    return obstacle;
  }
I love me a good ternary, but I will not be introducing that shorthand in the book. Hence the rather verbose shape assignment. With that function, I can define my three levels as:
  var levels = [];
  levels[0] = [
    buildObstacle('platform', 0, 0.3 * height * randomPlusMinus())
  ];
  levels[1] = [
    buildObstacle('platform', 0, 0.6 * height * randomPlusMinus()),
    buildObstacle('platform', 0, 0.3 * height * randomPlusMinus())
  ];
  levels[2] = [
    buildObstacle('platform', 0, 0.6 * height * randomPlusMinus()),
    buildObstacle('platform', 0.5 * width * randomPlusMinus(), 0.3 * height * randomPlusMinus()),
    buildObstacle('stalactite', 0.33 * width, 0),
    buildObstacle('stalactite', 0.66 * width, 0)    
  ];
I may be able to avoid complex data structures, but I cannot escape zero indexing. Shame.

Drawing the current level is as simple as running through each of the obstacles in the current level:
  var current_level = 0;
  function drawCurrentLevel() {
    var obstacles = levels[current_level];
    obstacles.forEach(function(obstacle) {
      scene.add(obstacle);
    });      
  }
  drawCurrentLevel();
As the player moves through levels, I will need to tear down the previous level before building the next level. Fortunately, Three.js supplies the very sane scene.remove() to accomplish this:
  function eraseOldLevel() {
    if (current_level === 0) return;
    var obstacles = levels[current_level-1];
    obstacles.forEach(function(obstacle) {
      scene.remove(obstacle);
    });      
  }
The combination of eraseOldLevel() and drawCurrentLevel() are sufficient to describe levelling up:
  player.addEventListener('collision', function(object) {
    if (object.isGoal) levelUp();
  });
  
  function levelUp() {
    current_level++;
    if (current_level > levels.length) return gameOver();
    eraseOldLevel();
    drawCurrentLevel();
  }
And that seems to work.

After adding simple code to move the goal after each level, I have a more challenging, three level game:


In the end, that is a lot of code. It is very likely too much for the chapter that already puts objects to use for the first time and has to build the rest of the game. Still, this seems a gentle way to introduce multi-level gaming so perhaps this might make into a second chapter. If I were inclined to make kids cry, I could introduce data structures here as well. Maybe just a wafer-thin sidebar.

(live code for the game so far)


Day #658