週刊 WebでMinec◯aft的なものを作る! ~第6週目~

  • 2
    いいね
  • 0
    コメント

1週間ぶりですね!今回も司会進行は、大学生プログラマのうーぴょん(さすらいうさぎ)が担当してきたいと思います!
先週は、視線の先にあるブロックを取得しましたね!これでブロックを置いたり壊したりするための準備がほぼ完了しました。しかし、うっかり先週の回で、そのコードを解説するのを忘れてしまったため今週やっていきたいと思います!

アジェンダ的な!

  1. ブロックを壊せるようにする
  2. ブロックを置けるようにする

Let's try!

ブロックを壊せるようにしよう!

これは非常に簡単です。視線の先にあるブロックは取得できているので、左クリックをして空気ブロックと置き換えれば良いのです!

js/application.js(抜粋)
  let [yaw, pitch] = [0, 0];
  let [moveX, moveZ] = [1, 0];
  let targetBlockPos = null; //これを追加
  let thePlayerWork = null; //これを追加
js/application.js(抜粋)
  //昔書いたコードと照らしてください・・・
  let setMarker = (pos) => {
    if(pos != null) {
      rendering.showSelectedBlockMarker(pos[0], pos[1], pos[2]);
      targetBlockPos = [pos[0], pos[1], pos[2]];
      if(thePlayerWork == 0) {
        world.setBlock(pos[0], pos[1], pos[2], Blocks.air);
        rendering.updateMesh(pos[0], pos[1], pos[2]);
      }
    } else {
      rendering.removeSelectedBlockMarker();
      targetBlockPos = null;
    }
  }
js/application.js(抜粋)
  //全部追加
  document.onmousedown = (e) => {
    if(e.buttons == 1) {
      thePlayerWork = 0;
      if(targetBlockPos != null) {
        world.setBlock(targetBlockPos[0], targetBlockPos[1], targetBlockPos[2], Blocks.air);
        rendering.updateMesh(targetBlockPos[0], targetBlockPos[1], targetBlockPos[2]);
      }
    }
  }

  document.onmouseup = () => {
    thePlayerWork = null;
  }

左クリックが押されている間中ずっとブロックを壊すというのを実現したかったため少し複雑になってしまいました。(jsのイベントハンドラにマウスのボタンが押されている間というイベントなかった)
なのでonmousedownでマウスが押されたことを示すフラグを立ててonmouseupでフラグを下ろしています。
ちなみにonmousedownはマウスのキーが押された時に発火するイベントonmouseupは離された時に発火するイベントです!

スクリーンショット 2016-10-19 7.41.00.png

ブロック壊れましたね!

ブロックを置けるようにしよう!

STEP1 置く面を取得しよう!

ブロックを壊す時と違って、ブロックを置く時には、視線の先のブロックの面を取得しなければなりません。まずはそのコードを書きましょう!

js/application.js
let getTargetBlockPos = (x, y, z) => {
    let pos = getPenetratingPosOnBlock(x, y, z);
    let getDistance = (a, b) => Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2));

    if(x < 0 || y < 0 || z < 0) {
      return null;
    }

    if(getDistance([rendering.posX, rendering.posY + 1.6, rendering.posZ], [x, y, z]) > 10) {
      return null;
    }

    if(world.getBlock(x, y, z) != Blocks.air) {
      let side = null;
      if(pos[0] != null && rendering.sightY < 0) {
        side = 0;
      } else if(pos[1] != null && rendering.sightX < 0) {
        side = 1;
      } else if(pos[2] != null && rendering.sightZ < 0) {
        side = 2;
      } else if(pos[3] != null && rendering.sightX > 0) {
        side = 3;
      } else if(pos[4] != null && rendering.sightZ > 0) {
        side = 4;
      } else if(pos[5] != null && rendering.sightY > 0) {
        side = 5;
      }
      return [[x, y, z], side];
    }

    if(pos[0] != null && rendering.sightY > 0) {
      return getTargetBlockPos(x, y + 1, z);
    } else if(pos[1] != null && rendering.sightX > 0) {
      return getTargetBlockPos(x + 1, y, z);
    } else if(pos[2] != null && rendering.sightZ > 0) {
      return getTargetBlockPos(x, y, z + 1);
    } else if(pos[3] != null && rendering.sightX < 0) {
      return getTargetBlockPos(x - 1, y, z);
    } else if(pos[4] != null && rendering.sightZ < 0) {
      return getTargetBlockPos(x, y, z - 1);
    } else if(pos[5] != null && rendering.sightY < 0) {
      return getTargetBlockPos(x, y - 1, z);
    }

    return null;
  }

面の番号は、上面が0、底面が5の他は、x軸方向から時計周りに1、2...とついています。

STEP2 この情報を元にブロックを置く!

あとは頑張ってコードを書きます!

js/application.js(抜粋)
  let targetBlockPos = null;
  let targetBlockSide = null; //これを追加
  let thePlayerWork = null;
js/application.js(抜粋)
  let getTargetBlockPos = (x, y, z) => {
    let pos = getPenetratingPosOnBlock(x, y, z); 
    let getDistance = (a, b) => Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2));

    if(x < 0 || y < 0 || z < 0) {
      return null;
    }   

    if(getDistance([rendering.posX, rendering.posY + 1.6, rendering.posZ], [x, y, z]) > 10) {
      return null;
    }   

    if(world.getBlock(x, y, z) != Blocks.air) {
      let side = null;
      if(pos[0] != null && rendering.sightY < 0) {
        side = 0;
      } else if(pos[1] != null && rendering.sightX < 0) {
        side = 1;
      } else if(pos[2] != null && rendering.sightZ < 0) {
        side = 2;
      } else if(pos[3] != null && rendering.sightX > 0) {
        side = 3;
      } else if(pos[4] != null && rendering.sightZ > 0) {
        side = 4;
      } else if(pos[5] != null && rendering.sightY > 0) {
        side = 5;
      }
      return [[x, y, z], side];
    }

    if(pos[0] != null && rendering.sightY > 0) {
      return getTargetBlockPos(x, y + 1, z);
    } else if(pos[1] != null && rendering.sightX > 0) {
      return getTargetBlockPos(x + 1, y, z);
    } else if(pos[2] != null && rendering.sightZ > 0) {
      return getTargetBlockPos(x, y, z + 1);
    } else if(pos[3] != null && rendering.sightX < 0) {
      return getTargetBlockPos(x - 1, y, z);
    } else if(pos[4] != null && rendering.sightZ < 0) {
      return getTargetBlockPos(x, y, z - 1);
    } else if(pos[5] != null && rendering.sightY < 0) {
      return getTargetBlockPos(x, y - 1, z);
    }

    return null;
  }
js/application.js(抜粋)
  let setMarker = (info) => {
    let pos = (info != null) ? info[0] : null;
    let side = (info != null) ? info[1] : null;

    if(info != null) {
      rendering.showSelectedBlockMarker(pos[0], pos[1], pos[2]);
      targetBlockPos = [pos[0], pos[1], pos[2]];
      targetBlockSide = side;
      if(thePlayerWork == 0) {
        world.setBlock(pos[0], pos[1], pos[2], Blocks.air);
        rendering.updateMesh(pos[0], pos[1], pos[2]);
      } else if(thePlayerWork == 1) {
        switch(side) {
          case 0:
            pos[1] += 1;
            break;
          case 1:
            pos[0] += 1;
            break;
          case 2:
            pos[2] += 1;
            break;
          case 3:
            pos[0] -= 1;
            break;
          case 4:
            pos[2] -= 1;
            break;
          case 5:
            pos[1] -= 1;
            break;
        }
        world.setBlock(pos[0], pos[1], pos[2], Blocks.dirt);
        rendering.updateMesh(pos[0], pos[1], pos[2]);
      }
    } else {
      rendering.removeSelectedBlockMarker();
      targetBlockPos = null;
      tatgetBlockside = null;
    }
  }
js/application.js(抜粋)
  document.onmousedown = (e) => {
    if(e.buttons == 1) {
      thePlayerWork = 0;
      if(targetBlockPos != null) {
        world.setBlock(targetBlockPos[0], targetBlockPos[1], targetBlockPos[2], Blocks.air);
        rendering.updateMesh(targetBlockPos[0], targetBlockPos[1], targetBlockPos[2]);
      }
    } else if(e.buttons == 2) {
      thePlayerWork = 1;
      if(targetBlockPos != null) {
        let pos = [targetBlockPos[0], targetBlockPos[1], targetBlockPos[2]];
        switch(targetBlockSide) {
          case 0:
            pos[1] += 1;
            break;
          case 1:
            pos[0] += 1;
            break;
          case 2:
            pos[2] += 1;
            break;
          case 3:
            pos[0] -= 1;
            break;
          case 4:
            pos[2] -= 1;
            break;
          case 5:
            pos[1] -= 1;
            break;
        }
        world.setBlock(pos[0], pos[1], pos[2], Blocks.dirt);
        rendering.updateMesh(pos[0], pos[1], pos[2]);
      }
    }
  }

  document.oncontextmenu = () => {
    return false;
  }

ほとんどそのままなので特に説明することはありません!
oncontextmenuは右クリックが押された時に発火するイベントです。ここでfalseを返すことにより右クリックを押した時に出てしまうメニューを出なくしています。

スクリーンショット 2016-10-19 8.35.43.png

こんな感じでブロックがおけます!

まとめ

今回は内容が少なめになりましたが、わりと重要な機能が実装できたと思います!
次回はエンティティを作っていきましょう!

  • onmousedown : マウスのボタンが押された時発火する
  • onmousedown : マウスのボタンが離された時発火する
  • oncontextmenu : 右クリックがされた時発火する。メニューを表示させたくない時に使うと便利

第7週目はこちらから

週刊 WebでMinec◯aft的なものを作る! ~第7週目~

第5週目はこちらから

週刊 WebでMinec◯aft的なものを作る! ~第5週目~