Sebastian Poręba's blog

3D Tetris with Three.js tutorial – part 2

Second part of tutorial is about adding static blocks. It will be short but very important.

Joined or separated?

Think about the way we play Tetris. When the block is moving, we transform and rotate it freely. Cubes that make block are clearly connected and it’s intuitive that their representation in code should be as well. On the other hand, when we try to complete a slice (in 2D – a row) and we succed, the cubes are removed and the block that was their origin doesn’t matter at this point. In fact, it shouldn’t matter – some boxes from a block may be removed and other not. Tracing an origin of a box would require constant splitting and merging geometries and trust me – that would be a crazy mess. In original 2D Tetris sometimes the color of a square was the indicator of the origin block. In 3D however we need a quick way to show z-axis and color is perfect for this.

In our game cubes will be connected when are dynamic and static when they are not. At the end of tutorial we will further optimize static cubes.

Adding a static block

Lets start with a moment when a moving block touches the floor (or another block). The moving block (with merged geometry of few cubes) is transformed into static, separated cubes that don’t move anymore. It’s convenient to keep these cubes in a 3D array.

Tetris.staticBlocks = [];
Tetris.zColors = [
  0x6666ff, 0x66ffff, 0xcc68EE, 0x666633, 0x66ff66, 0x9966ff, 0x00ff66, 0x66EE33, 0x003399, 0x330099, 0xFFA500, 0x99ff00, 0xee1289, 0x71C671, 0x00BFFF, 0x666633, 0x669966, 0x9966ff
Tetris.addStaticBlock = function(x,y,z) {
  if(Tetris.staticBlocks[x] === undefined) Tetris.staticBlocks[x] = [];
  if(Tetris.staticBlocks[x][y] === undefined) Tetris.staticBlocks[x][y] = [];

  var mesh = THREE.SceneUtils.createMultiMaterialObject(new THREE.CubeGeometry( Tetris.blockSize, Tetris.blockSize, Tetris.blockSize), [
    new THREE.MeshBasicMaterial({color: 0x000000, shading: THREE.FlatShading, wireframe: true, transparent: true}),
    new THREE.MeshBasicMaterial({color: Tetris.zColors[z]}) 
  ] );
  mesh.position.x = (x - Tetris.boundingBoxConfig.splitX/2)*Tetris.blockSize + Tetris.blockSize/2;
  mesh.position.y = (y - Tetris.boundingBoxConfig.splitY/2)*Tetris.blockSize + Tetris.blockSize/2;
  mesh.position.z = (z - Tetris.boundingBoxConfig.splitZ/2)*Tetris.blockSize + Tetris.blockSize/2;
  mesh.overdraw = true;
  Tetris.staticBlocks[x][y][z] = mesh;

There is a lot to explain here.

Colors and materials

Tetris.zColors keeps a list of colors that indicate position of a cube on z-axis. I’d like to have a good looking cube, so it should have a color AND outlined border. I’m going to use something that is not very popular in Three.js tutorials – multiMaterials. There is a function in Three.js SceneUtils that takes a geometry and an array (notice brackets []) of materials. If you look in Three.js source:

  createMultiMaterialObject : function ( geometry, materials ) {
  var i, il = materials.length, group = new THREE.Object3D();
  for ( i = 0; i < il; i ++ ) {
    var object = new THREE.Mesh( geometry, materials[ i ] );
    group.add( object );
  return group;

It’s a very simple hack that creates a mesh for every material. With pure WebGL there are better ways to achieve the same result (f.e. calling draw two times, once with gl.LINES and second with gl.something) but the usual use of this function is to for example to merge textures and materials at the same time – not different types of drawing.

Position in 3D space

Now, why the hell does position look like that?

mesh.position.x = (x - Tetris.boundingBoxConfig.splitX/2)*Tetris.blockSize + Tetris.blockSize/2;

Our board center on init was placed in the (0,0,0) point. It is not a very good spot, as it means that some cubes will have negative position and others positive. It would be better to specify a corner of an object in our case. Moreover, we would like to think of our boxes positions as a discrete values from 1 to 6, or at least 0 to 5. Three.js (and WebGL, OpenGL and everything else) uses its own units that rather relate to meters or pixels. If you remember, in config we put a value

Tetris.blockSize = boundingBoxConfig.width/boundingBoxConfig.splitX;

that is responsible for conversion. So as a summary:

 // transform 0-5 to -3 - +2
(x - Tetris.boundingBoxConfig.splitX/2)
 // scale to Three.js units
 // we specify cube center, not a corner - we have to shift position
 + Tetris.blockSize/2

Nice test

Our game is still very static, but you can open your console and run:

var i = 0, j = 0, k = 0, interval = setInterval(function() {if(i==6) {i=0;j++;} if(j==6) {j=0;k++;} if(k==6) {clearInterval(interval); return;} Tetris.addStaticBlock(i,j,k); i++;},30)

It should animate filling the board with cubes.

Keeping score

A small utility function to keep score:

Tetris.currentPoints = 0;
Tetris.addPoints = function(n) {
  Tetris.currentPoints += n;
  Tetris.pointsDOM.innerHTML = Tetris.currentPoints;

You can call it from console as well.

After this tutorial you should:

  • Know the difference between connected and separated geometries.
  • Understand basics of materials and how to mix them.
  • Understand what exactly does position mean in Three.js.

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

2 Responses so far.

  1. Mairead says:

    Hi, Your tutorial is really awesome but I think the createMultiMaterialObject method is deprecated. Have you managed to implement their workaround?

    I’m totally new to this so I am struggling to understand what they mean by using another geometry. Do I need to create two cubes, for each material type and layer them on top of each other?

    if I manage to make a solution I’ll come back and post it but was wondering if you’d come across this already

  2. vsvankhede says:

    thank you for this tutorials,
    i want to create fps type game using three.js can guide me from where to start and if you know the tutorial available for fps game so please send me link on my email address or if possible please post it on your site thanks in advance.

  • RSS
  • Facebook
  • Twitter

FAQ about Wordpress

This came as a surprise for me but gMap is ...

gMap 3.3.3 released

It was a looong time since I last visited gMap. ...

Talks for Google Dev

Two new slide decks appeared in lectures tab. This time with ...

Talks and lectures w

Every now and then I spend a weekend watching various ...

3D Tetris with Three

In the fifth part of tutorial we add some final ...

FAQ about Wordpress

This came as a surprise for me but gMap is ...

gMap 3.3.0 released

Christmas came early! New version of gMap is ready!

Lecture for GTUG: Ja

Today I gave a lecture for GTUG Krakow about optimizations in ...

Unit testing for jQu

In part 1 I described basics of unit testing in ...

Unit testing for jQu

In part 1 I described some basic concepts behind unit ...