[three.js]First Person Viewの作り方

仮想空間。常々憧れている身として、いつか作りたいと思い続けておりました…が、three.jsのサンプルを見てたら作れそうな気がしたので試してみました。First Person Shooting：FPSでおなじみの一人称視点、First Person Viewです。

なおthree.jsはおろか、グラフィックのプログラミングは初めて。見当違いの実装かもですが、平にご容赦を…

# （追記）

なんと、コメントいただけてしまいました。ありがとうございます。いただいたコメントを基に調査した内容を追記いたしました。

## 2019/06/05：公式のFirst Person Controls

[masato_makino](https://qiita.com/masato_makino)さん、ありがとうございます。（[対象のコメント](https://qiita.com/cranpun/items/bbb3f35cd21b03f9d290#comment-5356a30a850a8760235d)）

公式モジュールの中に一人称視点モジュールの「First Person Controls」というものがあるとのこと。早速触ってみました。（[デモ](https://silly-yonath-bcee7c.netlify.com/fpc/)）

ああ、車輪の再発明（劣化）…
せめてFirst Person Controls含むthree.jsの公式モジュール群について、賑やかしの記事にでもなれば幸いです。


# 出来上がり

キーボードのjlで左右に視点をふり、wasdで移動できます。空間はまっくら。

[デモ](https://silly-yonath-bcee7c.netlify.com/fpv/)

余談ですが、これまた初めて使うサービス、簡単と評判のnetlifyを試しました。何ならアカウントを作るほうが時間かかったくらいでした。評判通り簡単。

Githubは[こちら](https://github.com/cranpun/myfpv)。

# 環境

npmから持ってきました。three.jsは標準でTypescriptをサポートしているようなのでそれも一緒に。バンドラはparcel。サーバまでついてるので動作確認もラクラク。

* three
* typescript
* parcel-bundler

# 開発する機能

今回開発するのは以下の機能です。First Person Viewということでカメラ周りの機能です。

* 左右への視点変更
* 前後左右への移動

three.jsにはカメラが何種類かありますが、[PerspectiveCamera](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera)を使います。
立体空間なのでx,y,zと軸が3本ありますが、上下軸をy軸、平面をxz軸で作りました。

## 1. 左右への視点変更

カメラの視点は[lookAtメソッド](https://threejs.org/docs/#api/en/core/Object3D.lookAt)で制御できます。
今回は2方向、xz座標のみの視点変更です。

lookAtメソッドはPerspectiveCameraではなく、2個上の親のObject3Dクラスで定義されていましたので探す時にはご注意。ページ上部のObject3Dからたどれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23715/c12be181-3735-244c-29f6-31b24e586626.png)

`camera.lookAt(new Vector3(x,y,z))`でその座標にカメラの視点を設定できますが、現在向いている方向から視点をずらす＝lookAtメソッドにわたすためにずらした場所の座標をどう計算するのか？数学が苦手な人（自分）にはここからが難関…言い換えれば得意な人には蛇足ですので、次の章へどうぞ。

ずらした位置の取得、ここはかの有名な（？）三角関数の御力を借ります。Javascript標準の`Math.sin`、`Math.cos`で対応する値を求められます。その値を使うことで、ずらした座標は以下の図のような形で計算できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23715/67f2f643-9418-6c54-91e3-a1fc12ec6bed.png)

Javascriptのコードの場合は以下のようになります。なお、方向さえわかればよいので、ここでは距離は任意の値、LENとします。

```js
// nowangleは現在の角度を保存する変数
nowangle += Math.PI / 10; // 角度はラジアン。この例では18度。
const offsetx: number = LEN * Math.cos(nowangle); // x軸方向の差分
const offsetz: number = LEN * Math.sin(nowangle); // z軸方向の差分

// 新しい方向の座標の設定。
camera.lookAt(new Vector3(camera.position.x + offsetx, camera.position.y, camera.position.z + offsetz));
```

*実際のコードはFpvCamera.tsのメソッドupdateLook*


Javascriptのsin/cosは値が0になるとき0で返ってこなかったりと、細かいTIPSがありますが、キモは上記となります。

## 2. 前後左右への移動

カメラの位置は[positionプロパティ](https://threejs.org/docs/#api/en/core/Object3D.position)のsetメソッドで制御できます。

そして、移動先の値は現在の向きに応じて値を計算する必要があります。基本の考え方は向きと同じ三角関数です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23715/5a9f9863-d5d6-a9cf-3f82-2f3bb0363b21.png)

こうして求めた値を設定すれば移動OK…ではなく、移動した後にも視点の変更を加えます。そうしないと移動前の場所を眺めたままになってしまいます。先程の視点変更で用意しておいたnowangleを使って同じ処理を実行します。

まとめると以下のようなコードになります。例では前に進む処理です。座標移動の場合、LENは移動距離に相当します。

```js
// 前方向なので現在の角度をそのまま使う
const offsetx: number = LEN * Math.cos(nowangle); // x軸方向の差分
const offsetz: number = LEN * Math.sin(nowangle); // z軸方向の差分

// 新しい位置の座標を設定。posは現在位置を格納するVector3。
camera.position.set(camera.position.x + offsetx, camera.position.y, camera.position.z + offsetz);


// 新しい方向の座標の設定。posは現在位置を格納するVector3。
const lx: number = LEN * Math.cos(nowangle); // x軸方向の差分
const lz: number = LEN * Math.sin(nowangle); // z軸方向の差分

// 取得した差分を使って視点を変更
camera.lookAt(new Vector3(camera.position.x + lx, camera.position.y, camera.position.z + lz));
```

*実際のコードはFpvCamera.tsのメソッドupdatePos*

# おわりに

こんな感じで数学が苦手な人（自分）でも、なんとかかんとか、自由に仮想空間を移動できるようになりました。ちょっと前はブラウザでこんなことができるようになるとは思いもよらなかったですが…

three.jsは3Dモデルも取り込めるらしいのでもう少し賑やかな空間にできれば、と考えています。