ロゴシュミグラム

3Dテトリスの作り方の基礎

まずはシンプルな3Dテトリスを作ろう

解説がわかりやすいようにするため、シンプルなものを作成していきます。

各パーツや変数の名前

各パーツや変数の名前をつぎのように設定します。

3Dテトリスのパーツの名前

落下するブロックは block とし、床に落ちて固定されたブロックは fixedBlock とします。

また、落下ブロックの一辺のマス目の数を blockSideLength と名付けます。

3Dテトリスの落下ブロック

上の画像の場合、 blockSideLength は3です。

これらの設定を書いていきます。

index.html

// ゲーム画面となるcanvasの横幅と縦幅
const canvasWidth = 400;
const canvasHeight = 500;
// ステージのマス目の数
const widthLength = 3;
const heightLength = 4;
// 落下ブロックの一片のマス目
const blockSideLength = 3;
// 全体の縦幅
const fullHeightLength = heightLength + blockSideLength;
// 落下ブロックが1マス落ちる間隔(ミリ秒)
const dropInterval = 1000;

ここではステージの横のマス目を3個、縦のマス目を4個、落下ブロックの一片のマス目を3個に設定しているので、次のような感じになります。

3Dテトリスの落下ブロック

ゲームの状態の設定

ゲームが進行するとゲームオーバーになったり、ストップボタン押してを静止させたり、ゲームの状態が変化していきます。

そこでゲームの状態を次のように名づけておきます。

initial一番最初の状態
runningゲームがスタートしてブロックが落下している状態
stopストップボタンを押して止まっている状態
gameoverブロックがはみ出てゲームオーバーになった状態

そして今現在のゲームの状態を gameState に入れておきます。後で使う変数もここで用意しておきます。

index.html

let gameState = 'initial';
let block;
let squares;
let timeoutId;

Three.jsで使う色やマテリアルの設定

複数のオブジェクトで使いまわすマテリアル(素材)やジオメトリ(形)を用意しておきます。

ここでは色の指定するとき、 0xffffff のように書いていますが '#ffffff''white''"rgb(255, 255, 255)"' と書くこともできます。色の設定は自由に変更して大丈夫です。

index.html

// 背景色
const backgroundColor = 0x1A202C;
// ステージのマスの線の色
const gridColor = 0x888888;
// ステージ上部の枠線の色
const topLineColor = 0xff5b00;
// 落下ブロックのマテリアル
const blockMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
// 固定ブロックのマテリアル
const fixedBlockMaterial = new THREE.MeshLambertMaterial({ color: 0x00c3ff, transparent: true, opacity: 0.5 });
// 一面がそろったときの固定ブロックのマテリアル
const completeMaterial = new THREE.MeshLambertMaterial({ color: 0xFFD700, transparent: true, opacity: 0.5 });
// 落下ブロックがはみ出たときのマテリアル
const overflowMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 });
// ステージのマスの線のマテリアル
const lineMaterial = new THREE.LineBasicMaterial({ color: gridColor });
// ステージの壁のマテリアル
const wallMaterial = new THREE.MeshLambertMaterial({ color: 0xaaaaaa, transparent: true, opacity: 0.5, side: THREEDoubleSide });
// マス目1個のジオメトリ
const squareGeometry = new THREE.BoxGeometry(1, 1, 1);

マテリアルはブロックの陰影も表現したいので MeshLambertMaterial を使用していますが他のものでもかまいません。

固定ブロックや壁は半透明にしたいので transparent: trueopacity: 0.5 を設定しています。

Three.jsの初期設定

Three.jsで3D描画をおこなうのに必要な初期設定をするための関数を作っていきます。

index.html

let renderer;
let scene;
let camera;
let controls;
// レンダラーを作成する関数
const createRenderer = () => {
const canvas = document.getElementById('three-canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
canvas.style.maxWidth = canvasWidth + 'px';
renderer = new THREE.WebGLRenderer({ canvas: canvas });
};

関数内ではcanvasの大きさを設定してます。

CSSでcanvasを width: 100% としているため、400px以上には拡大させないよう maxWidth の値も設定します。

シーンを作成する関数

index.html

const createScene = () => {
scene = new THREE.Scene();
// 背景色の設定
scene.background = new THREE.Color(backgroundColor);
};

カメラとコントローラーを作成する関数

index.html

// カメラとコントローラーを作成する関数
const createCamera = () => {
// カメラの作成
camera = new THREE.PerspectiveCamera(5, canvasWidth / canvasHeight);
// カメラの位置
camera.position.set(widthLength * 12, fullHeightLength * 6, widthLength * 25);
// コントローラーの作成
controls = new OrbitControls(camera, renderer.domElement);
// これを設定すると視点移動がなめらかになる
controls.enableDamping = true;
controls.dampingFactor = 0.4;
// 視点の中心の設定
controls.target = new THREE.Vector3(widthLength / 2, fullHeightLength / 2, widthLength / 2);
};

カメラは_PerspectiveCamera_ を使って少しだけ遠近感が出るように設定しています。

コントローラーを作成することでゲーム画面をドラッグすると視点を移動できるようになります。

ライトを追加する関数

index.html

const addLight = () => {
// 第1引数は光の色、第2引数は光の強さ
const light = new THREE.DirectionalLight(0xFFFFFF, 10);
// ライトの位置の設定
light.position.set(widthLength, fullHeightLength * 10, widthLength * 4);
scene.add(light);
};

光の強さや位置を変えると影の付き方が変わります。

床や上部の線を追加する関数

index.html

const adGrids = () => {
// 床のグリッド
const bottomGrid = new THREE.GridHelper(widthLength, widthLength, gridColor, gridColor);
bottomGrid.position.set(widthLength / 2, 0, widthLength / 2);
scene.add(bottomGrid);
// ステージ上部の枠線 
const topGrid = new THREE.GridHelper(widthLength, 1, topLineColor, topLineColor);
topGrid.position.set(widthLength / 2, heightLength, widthLength / 2);
scene.add(topGrid);
};

床とステージ上部の線は GridHelper で作成しています。

GridHelper の引数には一辺の長さ、区分けする数、中心の線の色、他の線の色を渡します。

区分けする数を1とすることでステージ上部の枠線を作ることができます。

ステージ側面の縦線を追加する関数

index.html

const addVerticalLines = () => {
// 縦線のジオメトリを追加していく配列
const lineGeometries = [];
for (let i = 0; i <= widthLength; i++) {
// x軸の縦線のジオメトリ
const xlineGeometry = new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(i, 0, 0),
new THREE.Vector3(i, heightLength, 0),
]);
lineGeometries.push(xlineGeometry);
// z軸の縦線のジオメトリ
const zlineGeometry = new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(0, 0, i),
new THREE.Vector3(0, heightLength, i),
]);
lineGeometries.push(zlineGeometry);
}
// ジオメトリを結合して一つのジオメトリを生成
const linesGeometry = mergeGeometries(lineGeometries);
const lines = new THREE.LineSegments(linesGeometry, lineMaterial);
scene.add(lines);
};

複数の縦線のメッシュをシーンに追加するのではなく、複数のジオメトリを結合して一つのメッシュを作成してシーンに追加しています。

こうすることで描画の負荷を減らすことができます。

ステージ側面の壁

index.html

const addWall = () => {
// 床となるジオメトリ
const bottomWallGeometry = new THREE.PlaneGeometry(widthLength, widthLength);
// 角度と位置を調整する
bottomWallGeometry.rotateX(90 * Math.PI / 180);
bottomWallGeometry.translate(widthLength / 2, 0, widthLength / 2);
// 奥側の壁のジオメトリ
const backWallGeometry = new THREE.PlaneGeometry(widthLength, heightLength);
backWallGeometry.translate(widthLength / 2, heightLength / 2, 0);
// 左側の壁のジオメトリ
const leftWallGeometry = new THREE.PlaneGeometry(widthLength, heightLength);
leftWallGeometry.rotateY(90 * Math.PI / 180);
leftWallGeometry.translate(0, heightLength / 2, widthLength / 2);
// ジオメトリを結合して一つのジオメトリを生成
const wallGeometry = mergeGeometries([bottomWallGeometry, backWallGeometry, leftWallGeometry]);
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
scene.add(wall);
};

縦線と同じように複数のジオメトリを結合して一つのメッシュを作成してシーンに追加しています。

コントローラーで視点移動させるための関数

index.html

const animate = () => {
// コントローラーを更新して再描画する
controls.update();
renderer.render(scene, camera);
// この関数を繰り返し実行させる
requestAnimationFrame(animate);
};

この関数はコントローラーを更新、再描画を高速で繰り返します。

これにより画面をドラッグするとそれに応じて視点が移動しているように見えます。

初期描画する関数

index.html

const init3D = () => {
createRenderer();
createScene();
createCamera();
addLight();
addWall();
adGrids();
addVerticalLines();
animate();
};

この関数はこれまで作成した関数を実行して初期描画させます。

ここまでのプログラムを試そう

ここまでのプログラムを試すために init3D を実行してみましょう。

index.html

// お試し用。確認したら削除する
init3D()

ここまで書き込んだらindex.htmlをブラウザから開いてみてください。

画面にステージが表示されて、画面上をドラッグすると視点が移動できていれば成功です!

確認したら init3D() は消してください。