【Unity】Minecraftを作りたい！！Part1

# はじめに
Minecraftが好きなので，Unityでサンドボックス系オンラインゲームを作りたいなと思いました．
具体的にはMinecraftのようなサンドボックス + 2ちゃんねる or Twitterのようなコミュニケーションツールを目標にしています．
進捗報告&技術備忘録的な記事として書いています．

# 今回の進捗：マイクラ風コントローラー作成
マイクラ風のコントローラーはアセットとして既に無料で配布されているらしいのですが，作りたかったので作りました．(о´∀`о)
![controller.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696356/a2e303c7-3322-6435-1088-47ad7bde22ba.gif)
WASDで移動し，マウスで視点を振って，SPACEキーでジャンプします．
## ソースコード
※行き当たりばったりで書いてるので命名規則が雑だったり短縮できる冗長な部分もあるかもしれないですがご了承ください．

```C#:Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour{
    public float speed = 6f;
    public float mouseSensitivity = 0.1f;
    public float jumpPower = 4f;
    public GameObject camera;
    public GameObject cursor;
    private Vector3 viewVec = new Vector3(1, 0, 0);
    private Vector3 moveVec;
    private Vector3 camPos;
    private Rigidbody Rigidbody;
    private BoxCollider groundCollider;
    private bool isGround = false;

    void Start() {
        // Get component
        Rigidbody = GetComponent<Rigidbody>();
        groundCollider = GetComponent<BoxCollider>();
        // Viewpoint
        LookAtSet();
        // Hide mouse
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

    void Update() {
        // Camera
        float mouse_x = Input.GetAxis("Mouse X") * mouseSensitivity;
        float mouse_y = Input.GetAxis("Mouse Y") * mouseSensitivity;
        Vector3 nomVec = new Vector3(viewVec.z, viewVec.y, -viewVec.x);
        viewVec += nomVec.normalized * mouse_x;
        nomVec = Vector3.up;
        viewVec += nomVec.normalized * mouse_y;
        viewVec = viewVec.normalized;
        // Move
        bool isMove = false;
        if(Input.GetKey(KeyCode.W)){
            isMove = true;
            moveVec = new Vector3(viewVec.x, 0, viewVec.z).normalized * speed * Time.deltaTime;
            Rigidbody.velocity = new Vector3(moveVec.x, Rigidbody.velocity.y, moveVec.z);
        }
        if(Input.GetKey(KeyCode.S)){
            isMove = true;
            moveVec = new Vector3(-viewVec.x, 0, -viewVec.z).normalized * speed * Time.deltaTime;
            Rigidbody.velocity = new Vector3(moveVec.x, Rigidbody.velocity.y, moveVec.z);
        }
        if(Input.GetKey(KeyCode.A)){
            isMove = true;
            moveVec = new Vector3(-viewVec.z, 0, viewVec.x).normalized * speed * Time.deltaTime;
            Rigidbody.velocity = new Vector3(moveVec.x, Rigidbody.velocity.y, moveVec.z);
        }
        if(Input.GetKey(KeyCode.D)){
            isMove = true;
            moveVec = new Vector3(viewVec.z, 0, -viewVec.x).normalized * speed * Time.deltaTime;
            Rigidbody.velocity = new Vector3(moveVec.x, Rigidbody.velocity.y, moveVec.z);
        }
        if(!isMove){
            Rigidbody.velocity = new Vector3(0, Rigidbody.velocity.y, 0);
        }
        // Jump
        if(Input.GetKey(KeyCode.Space) && isGround){
            isGround = false;
            Rigidbody.AddForce(Vector3.up * jumpPower, ForceMode.Impulse);
        }
        // Viewpoint
        LookAtSet();
    }

    void OnTriggerEnter(Collider coll){
        isGround = true;
    }

    void OnTriggerExit(Collider coll){
        isGround = false;
    }

    void LookAtSet(){
        camPos = this.transform.position + viewVec;
        this.transform.LookAt(new Vector3(camPos.x, this.transform.position.y, camPos.z));
        camera.transform.position = new Vector3(camPos.x, this.transform.position.y + 0.5f, camPos.z);
        camera.transform.LookAt(camera.transform.position + viewVec);
    }
}
```
プレイヤーは1x2x1の直方体で，コンポーネントにはこのスクリプトとRigidBody，BoxColliderを2つ(体の当たり判定用と足元の地面設置判定用)をアタッチしています．地面設置用のBoxColliderはisTrrigerにチェックを付けています．

## 解説
### 視点変更
```C#
    // Viewpoint
    void LookAtSet(){
        camPos = this.transform.position + viewVec;
        this.transform.LookAt(new Vector3(camPos.x, this.transform.position.y, camPos.z));
        camera.transform.position = new Vector3(camPos.x, this.transform.position.y + 0.5f, camPos.z);
        camera.transform.LookAt(camera.transform.position + viewVec);
    }
```
これは，カメラの座標とカメラの向き，プレイヤーの向きを指定する関数となります．
`viewVec`にはプレイヤーの視点の長さ1のベクトルが保存されています．
`camPos`はXZ座標(上から見た状態)でのカメラの位置をXとZのパラメータに保存しています．
`<GameObject>.transform.LookAt()`はゲームオブジェクトに引数の3次元ベクトルの方向を向かせる関数です．Y座標はジャンプ以外で動いてほしくないので上記のように個別で指定しています．
上のように`<GameObject>.transform.LookAt()`の引数に(オブジェクトの座標)+(向きベクトル)を渡すと視点ベクトルで管理する時に見やすく書くことが出来ます．

### カメラ
```C#
        // Camera
        float mouse_x = Input.GetAxis("Mouse X") * mouseSensitivity;
        float mouse_y = Input.GetAxis("Mouse Y") * mouseSensitivity;
        Vector3 nomVec = new Vector3(viewVec.z, viewVec.y, -viewVec.x);
        viewVec += nomVec.normalized * mouse_x;
        nomVec = Vector3.up;
        viewVec += nomVec.normalized * mouse_y;
        viewVec = viewVec.normalized;
```
`Input.GetAxis("Mouse X")` `Input.GetAxis("Mouse Y")`によって毎フレームのマウスの移動距離を取得できます．
視点ベクトルにその法線のベクトルを足すと，こんな風に直角三角形の斜辺のようなベクトルが得られます．(下図)
これで視点の左右回転を再現しています．
![wedgrethtf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696356/ac47c26a-f7cb-5d4e-c338-86e8af5d313f.png)
また，視点ベクトルに上ベクトルを足すと，上下回転した後のベクトルが得られます．(下図では上ベクトルをマイナス倍している)
![drfthftykfsgnfdgty.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696356/00e0e19a-5f1c-517f-4ca1-bd0fcef51afa.png)
マウスの上下左右の移動量と正負を用いて(スカラー積)回転の向きと回転量を定義しています．
ただこのままでは視点を振るたびにベクトルが永遠に伸び続けるので，最後の行に`.nomalized`で正規化しています．
ここで視点ベクトルを計算して上の視点変更の関数で実際にカメラを動かしています．

### 移動
```C#
        // Move
        bool isMove = false;
        if(Input.GetKey(KeyCode.W)){
            isMove = true;
            moveVec = new Vector3(viewVec.x, 0, viewVec.z).normalized * speed * Time.deltaTime;
            Rigidbody.velocity = new Vector3(moveVec.x, Rigidbody.velocity.y, moveVec.z);
        }
        if(Input.GetKey(KeyCode.S)){
            isMove = true;
            moveVec = new Vector3(-viewVec.x, 0, -viewVec.z).normalized * speed * Time.deltaTime;
            Rigidbody.velocity = new Vector3(moveVec.x, Rigidbody.velocity.y, moveVec.z);
        }
        if(Input.GetKey(KeyCode.A)){
            isMove = true;
            moveVec = new Vector3(-viewVec.z, 0, viewVec.x).normalized * speed * Time.deltaTime;
            Rigidbody.velocity = new Vector3(moveVec.x, Rigidbody.velocity.y, moveVec.z);
        }
        if(Input.GetKey(KeyCode.D)){
            isMove = true;
            moveVec = new Vector3(viewVec.z, 0, -viewVec.x).normalized * speed * Time.deltaTime;
            Rigidbody.velocity = new Vector3(moveVec.x, Rigidbody.velocity.y, moveVec.z);
        }
        if(!isMove){
            Rigidbody.velocity = new Vector3(0, Rigidbody.velocity.y, 0);
        }
```
ここではWASDそれぞれ入力を受け付けて，移動ベクトルを計算し，プレイヤーに速度を与えています．(似通った処理だからまとめられそう…)
`moveVec`では前後左右のベクトルを計算するのですが，視点ベクトルを基準に前後左右を決めるため`viewVec`を使用して計算しています．
移動時の速度のYのパラメータは，`moveVec`のYが0となっているので個別で指定しています．
また，最後のif文ではキーを離した瞬間にピタッと止まらず少しスリップするのが嫌だったので速度を0に指定しています．

### ジャンプ
```C#
        // Jump
        if(Input.GetKey(KeyCode.Space) && isGround){
            isGround = false;
            Rigidbody.AddForce(Vector3.up * jumpPower, ForceMode.Impulse);
        }

...

    void OnTriggerEnter(Collider coll){
        isGround = true;
    }

    void OnTriggerExit(Collider coll){
        isGround = false;
    }
```
ジャンプに関する記述はシンプルで，地面に接地しているか確認して，接地時にスペースが押されているとジャンプをするという流れになっています．ジャンプ時にすぐ接地フラグをFalseにし，接地後にTrueに戻すことによって，押しっぱなしで連続ジャンプになるようにしてみました．
記事を書いているうちに`OnTriggerExit`(地面から離れた瞬間オフにする記述)の部分いらないんじゃねーかって気がしてきました．ガバガバソースコードですね．

# おわりに
今後は，
・プロシージャルマッピング(スクリプトによるマップ生成)
・PUN2の導入(PhotonUnityNetworking2というオンライン機能を実装するアセット)
・ブロックの設置
辺りをやっていきたいと思います．

…Part2書けるかなぁ()