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

No comments:

Post a Comment