ロゴシュミグラム

落下ブロックを操作する関数の作成

落下ブロックを移動させる関数

index.html

const moveBlock = (event) => {
// blockオブジェクトをコピーする
const movedBlock = structuredClone(block);
// 移動ボタンのvalueの値で移動方向を決める
switch (event.target.value) {
case 'left': movedBlock.position.x += -1;
break;
case 'right': movedBlock.position.x += 1;
break;
case 'front': movedBlock.position.z += 1;
break;
case 'back': movedBlock.position.z += -1;
break;
}
// 壁や固定ブロックと衝突していないかチェックする
if (!checkCollision(movedBlock)) {
// 衝突していなければ現在の落下ブロックと移動後の落下ブロックを交換する
replaceBlock(droppedBlock);
}
};

この関数はclickのイベントリスナーとして登録するので、引数にはclickイベントを受け取ります。

そして登録された移動ボタンのvalue属性には方向が設定されてます。

もし、それがright(右方向)であれば落下ブロックの位置をx軸方向に1増やし、front(手前方向)であれば位置をz軸方向に1減らしたりします。

落下ブロックを移動するには、まず移動したあとの落下ブロックを壁や固定ブロックと衝突しないかチェックしなければなりません。衝突しなければ移動させ、衝突するようであれば何もしません。

block をコピーした movedBlock に移動後の位置を設定します。それを使って衝突するか否かのチェックをおこないます。

衝突していなければ現在の落下ブロックと移動後の落下ブロックを交換します。

こうすることで落下ブロックが1マスずれて表示されるようになります。

や固定ブロックと衝突するかチェックする関数

index.html

const checkCollision = (block) => {
// 落下ブロックのすべての座標をチェックする
const isMobable = block.coordinates.some((coordinate) => {
const { x, y, z } = convertCoordinate(coordinate, block.position);
// x, y, zが壁と衝突している、または固定ブロックと衝突しているばtrueとなる
return checkWallCollision(x, y, z) || checkBlockCollision(x, y, z);
});
return isMobable;
};

引数に移動後の落下ブロックを受け取ります。

some 関数を使い、落下ブロックの座標が一つでも壁か固定ブロックに衝突していたら、この関数はtrueを返します。

座標が壁と衝突しているかチェックする関数

index.html

const checkWallCollision = (x, y, z) => {
return x < 0 || x >= widthLength || z < 0 || z >= widthLength || y < 0;
};

引数で渡されたx, y, zの座標が衝突しているか、つまりステージの外に出てしまっているのであればtrueを返します。

座標が固定ブロックと衝突しているかチェックする関数

index.html

const checkBlockCollision = (x, y, z) => {
return squares[y][z][x].visible && squares[y][z][x].material === fixedBlockMaterial;
};

引数で渡されたx, y, zの座標にブロックが存在しており、それが固定ブロックであればtrueを返します。

三次元のブロックを回転させる方法

次のようなブロックがあったとします。

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

このブロックのマス目の座標は次の通りです。


[
{ x:1, y:1, z:1 },
{ x:1, y:1, z:2 },
{ x:2, y:1, z:1 },
]

これを真ん中のマス目を中心にして奥側に90°回転させると次のような形になります。

3Dテトリスの回転後の落下ブロック形1

このブロックのマス目の座標は次の通りです。


[
{ x:1, y:1, z:1 },
{ x:1, y:2, z:1 },
{ x:2, y:1, z:1 },
]

これを一般化すると次のようになります。

回転方向回転後のx回転後のy回転後のz
xz|y-a-1|
手前x|z-a-1|y
|z-a-1|yx
zy|x-a-1|
  • 表中のx、y、zは回転前の座標です。
  • aは落下ブロックの1辺のマス目の数です( blockSideLength の値)
  • |...|は絶対値を表しています。

上の画像のケースでは、回転前の座標 { x:1, y:1, z:2 } から回転後の座標を上の表を使って計算してみると、 { x:1, y:2, z:1 } となることが確認できます。

落下ブロックを回転させる関数

index.html

const rotateBlock = (event) => {
// blockオブジェクトをコピーする
const rotatedBlock = structuredClone(block);
// 定数を計算しておく
const tmp = blockSideLength - 1;
// 回転後の座標たちを更新する
rotatedBlock.coordinates = block.coordinates.map((coordinate) => {
// 回転後のx
let rotatedX =
event.target.value === 'left' ? Math.abs(coordinate.z - tmp)
: event.target.value === 'right' ? coordinate.z : coordinate.x;
// 回転後のy
let rotatedY =
event.target.value === 'front' ? Math.abs(coordinate.z - tmp)
: event.target.value === 'back' ? coordinate.z : coordinate.y;
// 回転後のx
let rotatedZ =
event.target.value === 'back' ? Math.abs(coordinate.y - tmp)
:event.target.value === 'right' ? Math.abs(coordinate.x - tmp)
: event.target.value === 'left' ? coordinate.x : coordinate.y;
return { x: rotatedX, y: rotatedY, z: rotatedZ };
});
// 壁や固定ブロックと衝突しているかチェックする
if (!checkCollision(rotatedBlock)) {
// 衝突していなければ現在の落下ブロックと回転後の落下ブロックを交換する
replaceBlock(rotatedBlock);
}
};

この関数もclickのイベントリスナーとして登録します。

上記の moveBlock 関数と同じ流れで衝突するか否かをチェックして、しなければ回転させます。

イベントリスナーとして登録する

index.html

// クラス名がmove-buttonのボタンすべてにmoveBlockを登録する
document.querySelectorAll('.move-button').forEach((button) => {
button.addEventListener('click', moveBlock);
});
// クラス名がrotate‐buttonのボタンすべてにrotateBlockを登録する
document.querySelectorAll('.rotate-button').forEach((button) => {
button.addEventListener('click', rotateBlock);
});

クラス名が move-button の各ボタンをクリックすると moveBlock を実行し、 rotate-button の各ボタンをクリックすると rotateBlock を実行します。

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

ここまで書いてきたプログラムを試してみましょう。

index.html

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

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

移動ボタンを押したら落下ブロックが前後左右に動き、回転ボタンを押して回転したら成功です!(壁と衝突するようであれば動きません)

確認したら上記のコードは消してください。