HTML5 Zone is brought to you in partnership with:

Sebastian Poręba is a JS ninja and gamedev student, devoted to trying every possible technology in browser environment. Works with WebGL, server-side JS and physics engines. Sebastian is a DZone MVB and is not an employee of DZone and has posted 5 posts at DZone. You can read more from them at their website. View Full User Profile

3D Tetris with Three.js Tutorial – Part 5

05.04.2012
| 3271 views |
  • submit to reddit

In the fifth part of tutorial we add some final polish to the game.

Completed slices and score counting

This function will be quite long but simple. To check if a slice is completed I calculate the maximum number of occupied fields and check every slice (moving on z-axis) if it’s full. This way I can change size of the board and this function should be still working. Try to think about all your functions in such way – if something may ever change, make your code flexible.

Tetris.Board.checkCompleted = function() {
  var x,y,z,x2,y2,z2, fields = Tetris.Board.fields;
  var rebuild = false;
 
  var sum, expected = fields[0].length*fields.length, bonus = 0;
 
  for(z = 0; z < fields[0][0].length; z++) {
    sum = 0;
    for(y = 0; y < fields[0].length; y++) {
      for(x = 0; x < fields.length; x++) {
        if(fields[x][y][z] === Tetris.Board.FIELD.PETRIFIED) sum++;
      }
    }
    // to be continued

When the slice is full, we should remove it and shift all the following slices. To make sure that we don’t skip a shifted slice we decrease z once. To make the game more interesting, bonus points are granted if there are multiple slices completed at once.

  if(sum == expected) {
    bonus += 1 + bonus; // 1, 3, 7, 15...
 
    for(y2 = 0; y2 < fields[0].length; y2++) {
      for(x2 = 0; x2 < fields.length; x2++) {
        for(z2 = z; z2 < fields[0][0].length-1; z2++) {
          Tetris.Board.fields[x2][y2][z2] = fields[x2][y2][z2+1]; // shift
        }
        Tetris.Board.fields[x2][y2][fields[0][0].length-1] = Tetris.Board.FIELD.EMPTY;
      }
    }
    rebuild = true;
    z--;
  }
}
if(bonus) {
  Tetris.addPoints(1000 * bonus);
}

Now, even though we dealt with board information, we still have to make changes to Three.js geometries. We couldn’t do it in previous loop as it could rebuild the geometries twice or even more if multiple slices were completed at once. This loop checks every Tetris.Board.fields with corresponding Tetris.staticBlocks adding and removing geometries where needed.

  if(rebuild) {
    for(var z = 0; z < fields[0][0].length-1; z++) {
      for(var y = 0; y < fields[0].length; y++) {
        for(var x = 0; x < fields.length; x++) {
          if(fields[x][y][z] === Tetris.Board.FIELD.PETRIFIED && !Tetris.staticBlocks[x][y][z]) {
            Tetris.addStaticBlock(x,y,z);
          }
          if(fields[x][y][z] == Tetris.Board.FIELD.EMPTY && Tetris.staticBlocks[x][y][z]) {
            Tetris.scene.removeObject(Tetris.staticBlocks[x][y][z]);
            Tetris.staticBlocks[x][y][z] = undefined;
          }
        }
      }
    }
  }
};

Audio API

Adding audio is very simple with HTML5. Let’s start with adding <audio> elements to index.html.

<audio id="audio_theme" src="music/tetris.mp3" preload="auto"></audio>
<audio id="audio_move" src="music/move.mp3" preload="auto"></audio>
<audio id="audio_collision" src="music/collision.mp3" preload="auto"></audio>
<audio id="audio_gameover" src="music/gameover.mp3" preload="auto"></audio>
<audio id="audio_score" src="music/cash.mp3" preload="auto"></audio>

Using these files in JS is also easy. First create an object to store your sounds:

// before Tetris.init()
Tetris.sounds = {};

To call Audio API we have to retrieve these DOM elements.

// in Tetris.init()
Tetris.sounds["theme"] = document.getElementById("audio_theme");
Tetris.sounds["collision"] = document.getElementById("audio_collision");
Tetris.sounds["move"] = document.getElementById("audio_move");
Tetris.sounds["gameover"] = document.getElementById("audio_gameover");
Tetris.sounds["score"] = document.getElementById("audio_score");

There are numerous methods and you can create you own audio player but for our purposes play() and pause() is enough. You can probably guess where you should add the music :)

  • Tetris.sounds["theme"].play() – in Tetris.init(), right after initialization of sound object.
  • Tetris.sounds["theme"].pause() – in Tetris.start().
  • else {Tetris.sounds["move"].play();} – in Tetris.Block.move(), if there is no ground collision.
  • Tetris.sounds["collision"].play(); – in Tetris.Block.move(), if there is a ground collision.
  • Tetris.sounds["score"].play(); – in Tetris.addPoints().
  • Tetris.sounds["gameover"].play(); – in Tetris.Block.generate(), where we test for the lost game.

The end

That’s all folks! Our Tetris is fully functional now. I hope it was a fun way to learn Three.js. There are many topics like advanced geometries, shaders, lights, skeletal animation, etc., that were not covered here. I just wanted to show that to create a game they’re not always needed. Actually, the best fun I’ve ever had was with games like Tetris, where gameplay was not directly related to the graphics quality. Obviously, great games often comes with great looks, but if you are not having fun from the first to the last minute, not even the best graphics can help.

If you liked the series, please share and comment. If you want to learn more, you should probably use pure WebGL from now on. You may start with this tutorial. Check also "Building the game" by Brandon Jones.

After this tutorial you should:

  • Know how to detect and manage completed slices.
  • Know basics of Audio API.
  • Know how to have fun.
  • Start writing your own game.

Grab source from github

If you have trouble with any of these, check tutorial again or ask a question in the comments below.

Published at DZone with permission of Sebastian Poręba, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)