Over a million developers have joined DZone.

3D Tetris with Three.js tutorial - Part 2

DZone's Guide to

3D Tetris with Three.js tutorial - Part 2

· Web Dev Zone ·
Free Resource

Learn how to add document editing and viewing to your web app on .Net (C#), Node.JS, Java, PHP, Ruby, etc.

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.

Extend your web service functionality with docx, xlsx and pptx editing. Check out ONLYOFFICE document editors for integration.


Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}