週刊 WebでMinec◯aft的なものを作る! ~号外1~

  • 5
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

今週は、なんと2本立てでお送りします。今回も司会・進行etc.を担当するのは、大学生プログラマのうーぴょん(さすらいうさぎ)です!
今回は号外ということなのですが、一体何があったのでしょうか?最近、いろいろな話を聞くことがあったのですが、その話を聞いている限り自分の書いているコードの書き方もしかして古い?という結論にたどりつきました。
もともとうっすら気付いてはいたのですが、スルーしてきてしまったのでしっかり修正しようと思います!

アジェンダ的な!

  1. class構文
  2. let・const
  3. アロー関数
  4. 分割代入
  5. 変数展開
  6. 番外編

Let's Try!

class構文を使おう!

javascriptでは、今までprototypeをゴリゴリしてオブジェクト志向を実現してきたわけですが、他言語でおなじみclass構文が使えるようになったので使ってみましょう!

example.js
//こんな感じでクラスを宣言できるようになった
class Food {
  //いわゆるコンストラクタ関数
  constructor() {
  }

  //インスタンスメソッド
  eaten() {
  }

  //クラスメソッド
  static taste() {
  }
}

//継承
class Pizza extends Food {
  bake() {
  }
}

これを使ってコードを書き直します!

js/blocks/Block.js
class Block {
  constructor(blockName) {
    this.name = blockName;
  }

  registerTextures() {
  }

  setTextures() {
    var texture = new THREE.TextureLoader().load("/img/textures/blocks/" + this.name + ".png");
    this.material = new THREE.MeshBasicMaterial({color: 0xffffff, map: texture});
  }

  getMaterial() {
    return this.material;
  }
}
js/blocks/BlockBedrock.js
class BlockBedrock extends Block {
  constructor() {
    super("bedrock");
  }
}
js/blocks/BlockDirt.js
class BlockDirt extends Block {
  constructor() {
    super("dirt");
    this.textures = [];
  }

  registerTextures() {
    for(var i = 0; i < 6; ++i) {
      var texture = new THREE.TextureLoader().load("/img/textures/blocks/" + this.name + "_" + i + ".png");
      this.textures.push(new THREE.MeshBasicMaterial({color: 0xffffff, map: texture}));
    }
  }

  setTextures() {
    this.material = new THREE.MultiMaterial(this.textures);
  }
}
js/blocks/BlockRock.js
class BlockRock extends Block {
  constructor() {
    super("rock");
  }
}

なんかスッキリとしたコードになった気がします!

let・constを使ってみよう!

jsでも制御文・関数以外でブロック({}で囲まれた部分)を使えるようになりました。これによって、変数の有効範囲を絞ることができるようになり、変数が宣言されたブロックでのみ、使える変数を宣言するのがletです!
constは、その名の通り定数を宣言するのに使います!

今度はこれらを使ってみましょう!

js/application.js
window.onload = function() {
  let posX = 0, posY = 0, posZ = 0;
  let yaw = 0, pitch = 0;
  let sightX = 1, sightY = 0, sightZ = 0;
  let moveX = 1, moveZ = 0;
  let renderer = new THREE.WebGLRenderer({antialias: true});
  let scene = new THREE.Scene();
  let camera = new THREE.PerspectiveCamera(40, 800 / 480);
  let cube = new THREE.CubeGeometry(1, 1, 1);
  let light = new THREE.AmbientLight(0xffffff);
  let updateCanvas = function() {
    requestAnimationFrame(updateCanvas);

    camera.position.set(posX, posY + 1.6, posZ);
    camera.lookAt(new THREE.Vector3(posX + sightX, posY + 1.6 + sightY, posZ + sightZ));
    renderer.render(scene, camera);
  }

  renderer.setSize(800, 480);
  renderer.setClearColor(0xffffff);
  document.getElementById("canvas_wrapper").appendChild(renderer.domElement);


  for(let x = -3; x <= 3; ++x) {
    for(let z = -3; z <= 3; ++z) {
      let meshDirt = new THREE.Mesh(cube, Blocks.dirt.getMaterial());
      let meshRock = new THREE.Mesh(cube, Blocks.rock.getMaterial());
      let meshBedrock = new THREE.Mesh(cube, Blocks.bedrock.getMaterial());

      meshDirt.position.set(x, 0, z);
      meshRock.position.set(x, -1, z);
      meshBedrock.position.set(x, -2, z);

      scene.add(meshDirt);
      scene.add(meshRock);
      scene.add(meshBedrock);
    }
  }

  scene.add(light);

  document.onkeydown = function(e) {
    if(e.keyCode == 87) {
      posX += 0.3 * moveX;
      posZ += 0.3 * moveZ;
    } else if(e.keyCode == 65) {
      posX += 0.3 * moveZ;
      posZ -= 0.3 * moveX;
    } else if(e.keyCode == 83) {
      posX -= 0.3 * moveX;
      posZ -= 0.3 * moveZ;
    } else if(e.keyCode == 68) {
      posX -= 0.3 * moveZ;
      posZ += 0.3 * moveX;
    } else if(e.keyCode == 32 && e.shiftKey) {
      posY -= 0.3;
    } else if(e.keyCode == 32) {
      posY += 0.3;
    }
  }

  document.onmousemove = function(e) {
    let deltaX = e.momentX || e.webkitMovementX || e.mozMovementX || e.movementX;
    let deltaY = e.momentY || e.webkitMovementY || e.mozMovementY || e.movementY;
    const radian = Math.PI / 180;

    yaw += deltaX / 0.8;
    pitch -= deltaY / 0.8;

    if(pitch > 90) {
      pitch = 90;
    } else if(pitch < -90) {
      pitch = -90;
    }

    sightX = Math.cos(radian * pitch) * Math.cos(radian * yaw);
    sightY = Math.sin(radian* pitch);
    sightZ = Math.cos(radian * pitch) * Math.sin(radian * yaw);
    moveX = Math.cos(radian * yaw);
    moveZ = Math.sin(radian * yaw);
  }

  updateCanvas(); 
}
js/blocks/Block.js
class Block {
  constructor(blockName) {
    this.name = blockName;
  }

  registerTextures() {
  }

  setTextures() {
    let texture = new THREE.TextureLoader().load("/img/textures/blocks/" + blockName + ".png");
    this.material = new THREE.MeshBasicMaterial({color: 0xffffff, map: texture});
  }

  getMaterial() {
    return this.material;
  }
}
js/blocks/BlockDirt.js
class BlockDirt extends Block {
  constructor() {
    super("dirt");
    this.textures = [];
  }

  registerTextures() {
    for(let i = 0; i < 6; ++i) {
      let texture = new THREE.TextureLoader().load("/img/textures/blocks/" + this.name + "_" + i + ".png");
      this.textures.push(new THREE.MeshBasicMaterial({color: 0xffffff, map: texture}));
    }
  }

  setTextures() {
    this.material = new THREE.MultiMaterial(this.textures);
  }
}
js/blocks/Blocks.js
const Blocks = {
  dirt: new BlockDirt(),
  rock: new BlockRock(),
  bedrock: new BlockBedrock()
}

for(let blockName in Blocks) {
  Blocks[blockName].registerTextures();
  Blocks[blockName].setTextures();
}

これで、定数の作成と変数の参照範囲を制限できました!

アロー関数を使おう!

アロー関数を使うと関数の宣言が少々短くなります。
例で見ていきましょう!

example.js
//これが今までの関数の宣言
var f = function() {
  //文
}

//アロー関数を使うと
var f = () => {
  //文
}

微妙に短くなりました。
いまいちメリットが伝わりにくいので、もう少し見ていきましょう!

example.js
//変数が1つの場合()は省略できるので・・・
$("#button1").click((e) => {
  console.log("押された");
  ++i;
});

//文が1行だと{}/returnが省略できるので・・・
["hello", "world!", "javascript"].map(x => x.length)
// => [5, 6, 10]

非常にスッキリと書けますね!
注意点は、thisが今までと変わる点です!(宣言されたブロックでのthisに束縛)

これを使ってみましょう!

js/application.js
window.onload = () => {
  let posX = 0, posY = 0, posZ = 0;
  let yaw = 0, pitch = 0;
  let sightX = 1, sightY = 0, sightZ = 0;
  let moveX = 1, moveZ = 0;
  let renderer = new THREE.WebGLRenderer({antialias: true});
  let scene = new THREE.Scene();
  let camera = new THREE.PerspectiveCamera(40, 800 / 480);
  let cube = new THREE.CubeGeometry(1, 1, 1);
  let light = new THREE.AmbientLight(0xffffff);
  let updateCanvas = () => {
    requestAnimationFrame(updateCanvas);

    camera.position.set(posX, posY + 1.6, posZ);
    camera.lookAt(new THREE.Vector3(posX + sightX, posY + 1.6 + sightY, posZ + sightZ));
    renderer.render(scene, camera);
  }

  renderer.setSize(800, 480);
  renderer.setClearColor(0xffffff);
  document.getElementById("canvas_wrapper").appendChild(renderer.domElement);


  for(let x = -3; x <= 3; ++x) {
    for(let z = -3; z <= 3; ++z) {
      let meshDirt = new THREE.Mesh(cube, Blocks.dirt.getMaterial());
      let meshRock = new THREE.Mesh(cube, Blocks.rock.getMaterial());
      let meshBedrock = new THREE.Mesh(cube, Blocks.bedrock.getMaterial());

      meshDirt.position.set(x, 0, z);
      meshRock.position.set(x, -1, z);
      meshBedrock.position.set(x, -2, z);

      scene.add(meshDirt);
      scene.add(meshRock);
      scene.add(meshBedrock);
    }
  }

  scene.add(light);

  document.onkeydown = (e) => {
    if(e.keyCode == 87) {
      posX += 0.3 * moveX;
      posZ += 0.3 * moveZ;
    } else if(e.keyCode == 65) {
      posX += 0.3 * moveZ;
      posZ -= 0.3 * moveX;
    } else if(e.keyCode == 83) {
      posX -= 0.3 * moveX;
      posZ -= 0.3 * moveZ;
    } else if(e.keyCode == 68) {
      posX -= 0.3 * moveZ;
      posZ += 0.3 * moveX;
    } else if(e.keyCode == 32 && e.shiftKey) {
      posY -= 0.3;
    } else if(e.keyCode == 32) {
      posY += 0.3;
    }
  }

  document.onmousemove = (e) => {
    let deltaX = e.momentX || e.webkitMovementX || e.mozMovementX || e.movementX;
    let deltaY = e.momentY || e.webkitMovementY || e.mozMovementY || e.movementY;
    const radian = Math.PI / 180;

    yaw += deltaX / 0.8;
    pitch -= deltaY / 0.8;

    if(pitch > 90) {
      pitch = 90;
    } else if(pitch < -90) {
      pitch = -90;
    }

    sightX = Math.cos(radian * pitch) * Math.cos(radian * yaw);
    sightY = Math.sin(radian* pitch);
    sightZ = Math.cos(radian * pitch) * Math.sin(radian * yaw);
    moveX = Math.cos(radian * yaw);
    moveZ = Math.sin(radian * yaw);
  }

  updateCanvas(); 
}

まぁ、function削るくらいしかできなかったので、少し綺麗になったくらいかな?

分割代入を使おう!

1行で複数の変数に値を代入できます!

example.js
//この文と
var a = 10;
var b = 50;
//この文は同じ
var [a, b] = [10, 50];

これだけです!あまり使い過ぎると微妙そうなので、ほどほどに使ってみましょう!

js/application.js
window.onload = () => {
  let [posX, posY, posZ] = [0, 0, 0];
  let [yaw, pitch] = [0, 0];
  let [sightX, sightY, sightZ] = [1, 0, 0];
  let [moveX, moveZ] = [1, 0];
  let renderer = new THREE.WebGLRenderer({antialias: true});
  let scene = new THREE.Scene();
  let camera = new THREE.PerspectiveCamera(40, 800 / 480);
  let cube = new THREE.CubeGeometry(1, 1, 1);
  let light = new THREE.AmbientLight(0xffffff);
  let updateCanvas = () => {
    requestAnimationFrame(updateCanvas);

    camera.position.set(posX, posY + 1.6, posZ);
    camera.lookAt(new THREE.Vector3(posX + SightX, posY + 1.6 + SightY, posZ + SightZ));
    renderer.render(scene, camera);
  }

  renderer.setSize(800, 480);
  renderer.setClearColor(0xffffff);
  document.getElementById("canvas_wrapper").appendChild(renderer.domElement);


  for(let x = -3; x <= 3; ++x) {
    for(let z = -3; z <= 3; ++z) {
      let meshDirt = new THREE.Mesh(cube, Blocks.dirt.getMaterial());
      let meshRock = new THREE.Mesh(cube, Blocks.rock.getMaterial());
      let meshBedrock = new THREE.Mesh(cube, Blocks.bedrock.getMaterial());

      meshDirt.position.set(x, 0, z);
      meshRock.position.set(x, -1, z);
      meshBedrock.position.set(x, -2, z);

      scene.add(meshDirt);
      scene.add(meshRock);
      scene.add(meshBedrock);
    }
  }

  scene.add(light);

  document.onkeydown = (e) => {
    if(e.keyCode == 87) {
      posX += 0.3 * moveX;
      posZ += 0.3 * moveZ;
    } else if(e.keyCode == 65) {
      posX += 0.3 * moveZ;
      posZ -= 0.3 * moveX;
    } else if(e.keyCode == 83) {
      posX -= 0.3 * moveX;
      posZ -= 0.3 * moveZ;
    } else if(e.keyCode == 68) {
      posX -= 0.3 * moveZ;
      posZ += 0.3 * moveX;
    } else if(e.keyCode == 32 && e.shiftKey) {
      posY -= 0.3;
    } else if(e.keyCode == 32) {
      posY += 0.3;
    }
  }

  document.onmousemove = (e) => {
    let deltaX = e.momentX || e.webkitMovementX || e.mozMovementX || e.movementX;
    let deltaY = e.momentY || e.webkitMovementY || e.mozMovementY || e.movementY;
    const radian = Math.PI / 180;

    yaw += deltaX / 0.8;
    pitch -= deltaY / 0.8;

    if(pitch > 90) {
      pitch = 90;
    } else if(pitch < -90) {
      pitch = -90;
    }

    [sightX, sightY, sightZ] = [Math.cos(radian * pitch) * Math.cos(radian * yaw), Math.sin(radian* pitch), Math.cos(radian * pitch) * Math.sin(radian * yaw)];
    [moveX, moveZ] = [Math.cos(radian * yaw), Math.sin(radian * yaw)];
  }

  updateCanvas(); 
}

変数展開を使おう!

jsでも文字列内の変数展開ができるようになりました!

example.js
var i = 20;
//今まで
console.log("i = " + i + "です!");

//新しい方法
console.log(`i = ${i}です!`);

スッキリと書けるようになりましたね!では書き換えてみましょう!

js/application.js
window.onload = () => {
  let [posX, posY, posZ] = [0, 0, 0];
  let [yaw, pitch] = [0, 0];
  let [sightX, sightY, sightZ] = [1, 0, 0];
  let [moveX, moveZ] = [1, 0];
  let renderer = new THREE.WebGLRenderer({antialias: true});
  let scene = new THREE.Scene();
  let camera = new THREE.PerspectiveCamera(40, 800 / 480);
  let cube = new THREE.CubeGeometry(1, 1, 1);
  let light = new THREE.AmbientLight(0xffffff);
  let updateCanvas = () => {
    requestAnimationFrame(updateCanvas);

    camera.position.set(posX, posY + 1.6, posZ);
    camera.lookAt(new THREE.Vector3(posX + sightX, posY + 1.6 + sightY, posZ + sightZ));
    renderer.render(scene, camera);
  }

  renderer.setSize(800, 480);
  renderer.setClearColor(0xffffff);
  document.getElementById("canvas_wrapper").appendChild(renderer.domElement);


  for(let x = -3; x <= 3; ++x) {
    for(let z = -3; z <= 3; ++z) {
      let meshDirt = new THREE.Mesh(cube, Blocks.dirt.getMaterial());
      let meshRock = new THREE.Mesh(cube, Blocks.rock.getMaterial());
      let meshBedrock = new THREE.Mesh(cube, Blocks.bedrock.getMaterial());

      meshDirt.position.set(x, 0, z);
      meshRock.position.set(x, -1, z);
      meshBedrock.position.set(x, -2, z);

      scene.add(meshDirt);
      scene.add(meshRock);
      scene.add(meshBedrock);
    }
  }

  scene.add(light);

  document.onkeydown = (e) => {
    if(e.keyCode == 87) {
      posX += 0.3 * moveX;
      posZ += 0.3 * moveZ;
    } else if(e.keyCode == 65) {
      posX += 0.3 * moveZ;
      posZ -= 0.3 * moveX;
    } else if(e.keyCode == 83) {
      posX -= 0.3 * moveX;
      posZ -= 0.3 * moveZ;
    } else if(e.keyCode == 68) {
      posX -= 0.3 * moveZ;
      posZ += 0.3 * moveX;
    } else if(e.keyCode == 32 && e.shiftKey) {
      posY -= 0.3;
    } else if(e.keyCode == 32) {
      posY += 0.3;
    }
  }

  document.onmousemove = (e) => {
    let deltaX = e.momentX || e.webkitMovementX || e.mozMovementX || e.movementX;
    let deltaY = e.momentY || e.webkitMovementY || e.mozMovementY || e.movementY;
    const radian = Math.PI / 180;

    yaw += deltaX / 0.8;
    pitch -= deltaY / 0.8;

    if(pitch > 90) {
      pitch = 90;
    } else if(pitch < -90) {
      pitch = -90;
    }

    [sightX, sightY, sightZ] = [Math.cos(radian * pitch) * Math.cos(radian * yaw), Math.sin(radian* pitch), Math.cos(radian * pitch) * Math.sin(radian * yaw)];
    [moveX, moveZ] = [Math.cos(radian * yaw), Math.sin(radian * yaw)];
  }

  updateCanvas(); 
}
js/blocks/Block.js
class Block {
  constructor(blockName) {
    this.name = blockName;
  }

  registerTextures() {
  }

  setTextures() {
    let texture = new THREE.TextureLoader().load(`/img/textures/blocks/${this.name}.png`);
    this.material = new THREE.MeshBasicMaterial({color: 0xffffff, map: texture});
  }

  getMaterial() {
    return this.material;
  }
}
js/blocks/BlockDirt.js
class BlockDirt extends Block {
  constructor() {
    super("dirt");
    this.textures = [];
  }

  registerTextures() {
    for(let i = 0; i < 6; ++i) {
      let texture = new THREE.TextureLoader().load(`/img/textures/blocks/${this.name}_${i}.png`);
      this.textures.push(new THREE.MeshBasicMaterial({color: 0xffffff, map: texture}));
    }
  }

  setTextures() {
    this.material = new THREE.MultiMaterial(this.textures);
  }
}

番外編

今まで、THREE.CubeGeometryで立方体を作ってきましたが、THREE.BoxGeometryが現在の書き方のようなので修正します!

js/application.js(抜粋)
let cube = new THREE.BoxGeometry(1, 1, 1);

まとめ

極力、新しい書き方で書きましょう!

第3週目はこちら

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

第4週目はこちら

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