Unity+VRTKにおいてボールと反発するラケットを作成する

# はじめに
Unity+VRTKで，VR空間において，モーションコントローラーに追従するラケットを振り回し，ボールを打ち返すゲームを作ったが，ラケットとボールと思うように反発せずハマったためメモした．ラケットに限らず，VR空間でバットなどを振り回すゲームにも応用できそう．

うまく反発しない原因としては．Unityにおいて，オブジェクト同士の正しい反発を計算するには，ボールとラケットの`Rigidbody.velocity`変数に正しく速度が設定されている必要があるが，ラケット側のそれは常に`Vector3.zero`であることが考えられる．

ただし，スクリプトでラケットの`Rigidbody`コンポーネントを直接いじるとラケットが変な動きをしそうなので，ラケット自体に速度は設定せず，ラケットと同じ位置に適切な速度が設定されたオブジェクトを配置することで，跳ね返りを実現した．

# 下準備
Unityプロジェクトを作成し，開発環境にあわせてVRTKが使えるようにセットアップする．

# ラケットの作成
1. シーンに空オブジェクトを作成し，名前を`Racket`に変更．`Transform`をリセットしておく．
2. 子にCylinderとCubeを配置し，それぞれ名前を`Face`，`Grip`等に変更．それぞれの`Transform`を適当にいじりラケットの形にする，
3. このままではコライダーが正しく設定されないので，`Grip`オブジェクトの`Box Collider`コンポーネントを削除する．`Face`オブジェクトの`Capsule Collider`を削除し，代わりに`Mesh Collider`をアタッチし，`Convex`にチェックする．
4. 適当なPhysic Materialを作成し，コライダーのMaterialにセットする．

# コントローラーの作成
1. シーンに2つの空のオブジェクトを作成し，それぞれ`RightController`，`LeftController`などに変更．
2. `Racket`オブジェクトをどちらかの子にする．
3. `VRTK_SDK Manager`の`Script Aliases`に作成した2つのオブジェクトを登録する．
4. シーンを再生し，コントローラーを動かすとラケットも同様に動くことを確認する．
5. グリップの位置が変な場合，`Racket`オブジェクトの`Transform`を適当にいじって調整する．

ここまででこんな感じになる．マテリアルはなんでも良い．

![画面1.PNG](https://qiita-image-store.s3.amazonaws.com/0/180397/2e19f32f-8e63-b6a6-c912-378f1e21b64c.png)

# ボールを打ってみる
1. シーン中にSphereを配置し，`Rigidbody`コンポーネントをアタッチする．とりあえずは`Use Gravity`のチェックを外しておく．
2. `Scale`を`(0.1, 0.1, 0.1)`等に変更してボールっぽくする．ボールを前方 (+z方向) に配置する．
3. ラケットと同様に，適当なPhysic Materialを作成し，コライダーにセットする．
4. 以下のようなスクリプトをアタッチして，動いてくるボールを打ってみる．
5. おそらくうまく跳ね返らない．

```csharp:Ball.cs
void Start() {
    GetComponent<Rigidbody>().AddForce(Vector3.back);
}
```

# ラケットに追従するオブジェクトを作成する
ここで`Rigidbody`を持ったオブジェクトを作成．適切な速度を設定し，ラケットに追従するオブジェクトを作成する．これを用いてボールが跳ね返るようにする．

1. `Face`オブジェクトを複製し，シーン中に適当に配置．`FaceFollower`等に名前を変更する．
2. `Rigidbody`コンポーネントをアタッチし，`Use Gravity`のチェックを外す．
3. 適当にわかりやすいマテリアルに変更する．
4. `Face`オブジェクトからはColliderコンポーネントを削除する．
5. `Face`オブジェクト，`FaceFollower`オブジェクトに以下のようなスクリプトをアタッチし，`FaceFollower`が自身の`Rigidbody`コンポーネントに適切な速度を設定しながら，`Face`を追従するようにする．
6. `FaceFollower`をプレハブ化し，シーンから削除．`faceFollowerPrefab`に作成したプレハブを登録する．

```csharp:RacketFace.cs
public class RacketFace : MonoBehavior {
    [SerializeField]
    GameObject faceFollowerPrefab;

    private GameObject faceFollower;

    void OnEnable(){
        faceFollower = Instantiate(faceFollowerPrefab, transform.position, transform.rotation);
        faceFollower.GetComponent<RacketFaceFollower>().SetTarget(gameObject);
    }

    void OnDisable(){
        Destroy(faceFollower);
    }
}
```


```csharp:RacketFaceFollower.cs
[RequireComponent(typeof(Rigidbody))]
public class RacktFaceFollower : MonoBehavior{
    [SerializeField]
    float sensitivity = 50f;

    private Rigidbody rb;
    private GameObject target;
    private Vector3 velocity;

    void Awake(){
        rb = GetComponent<Rigidbody>();
    }

    public void SetTarget(GameObject target){
        this.target = target;
    }

    void FixedUpdate(){
        if(target != null){
            velocity = (target.transform.position - rb.transform.position) * sensitivity;
            rb.velocity = velocity;
            rb.transform.rotation = transform.rotation;
            transform.rotation = target.transform.rotation;
        }
    }

}
```

シーンを再生するとボールをうまく打てるようになっているはず．`FaceFollower`が変な動きをしていたら，`RacketFaceFollower.sensitivity`値を調節する．うまくいけば，`FaceFollower`の`Mesh Renderer`を無効にして完成．

# 参考リンク
https://unity3d.college/2016/04/11/baseball-bat-physics-unity/