OculusGoでオブジェクトを掴んで移動させる、放り投げる

OculusGoでトリガーを押しているあいだオブジェクトを掴んで移動させるようにする方法。

環境: Unity 2018.1.5f1 (64-bit) / OculusUtilities(1.26.0 2018/06/20)

### コード

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LaserPointer : MonoBehaviour {

	[SerializeField]
	private Transform  _rightPointer = null;
	[SerializeField]
	private Transform  _leftPointer = null;

	// オブジェクトを掴む
	private GameObject _grabObject = null;
	private Vector3 _grabOffset;
	private float _grabDistance = 0;
	private Vector3 _gravPrevFramePos;

	// 現在アクティブな左右のどちらかのコントロールを得る
	private Transform GetController() {
		var controller = OVRInput.GetActiveController();
		return (controller == OVRInput.Controller.RTrackedRemote ? _rightPointer : _leftPointer);
	}
	
	// Update is called once per frame
	void Update () {
		var controller = GetController();

		// コントローラーから前に伸ばしたRayを作成
		var pointerRay = new Ray(controller.position, controller.transform.forward);

		// Rayがヒットした位置を取得
		RaycastHit hitInfo;
		if (Physics.Raycast(pointerRay, out hitInfo, _maxDistance)) {
			// ヒットしたオブジェクトを取得
			GameObject hitObj = hitInfo.collider.gameObject;
			
			// トリガーボタンを押した時
			if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) {
				if (hitObj.name != "Plane") { // 地面は掴めない
					_grabObject = hitObj;
					_grabDistance = hitInfo.distance;
					_grabOffset = hitObj.transform.position - hitInfo.point; // ヒットした場所からオブジェクト中心までの距離
					_gravPrevFramePos = hitObj.transform.position;
				}
			}
			
		}

		// 掴んだオブジェクトを移動
		if (_grabObject != null) {
			// 上下タッチで距離変更
			Vector2 pt = OVRInput.Get(OVRInput.Axis2D.PrimaryTouchpad); ///タッチパッドの位置
	        if (pt.y > +0.6 && (-0.5 < pt.x && pt.x < +0.5))  //上側？
	        {
				_grabDistance += 0.1f;
			}
	        else if (pt.y < -0.9 && (-0.5 < pt.x && pt.x < +0.5)) // 下側？
	        {
				_grabDistance -= 0.1f;
				if (_grabDistance < 0.1) { _grabDistance = 0.1f; }
	        } 

			// 移動
			_gravPrevFramePos = _grabObject.transform.position;
			_grabObject.transform.position = pointerRay.GetPoint(_grabDistance) + _grabOffset;
			_grabObject.GetComponent<Rigidbody>().velocity = Vector3.zero;
		}

		// 掴んだオブジェクトを離す
		if (OVRInput.GetUp(OVRInput.Button.PrimaryIndexTrigger)) {
			// トリガーボタンを離した時
			Vector3 force = _grabObject.transform.position - _gravPrevFramePos;
			_grabObject.GetComponent<Rigidbody>().velocity = force * 30f;
			_grabObject = null;
		}

	}
}
```

## 説明

### 1. コントローラーからRayを伸ばして掴むオブジェクトを取得

```C#
		var pointerRay = new Ray(controller.position, controller.transform.forward);

		// Rayがヒットした位置を取得
		RaycastHit hitInfo;
		if (Physics.Raycast(pointerRay, out hitInfo, _maxDistance)) {
			// ヒットしたオブジェクトを取得
			GameObject hitObj = hitInfo.collider.gameObject;
```

これはレーザーポインタを作成するときなどと同じ処理。

### 2. レーザーを伸ばした先のGameObjectの情報を「掴んでいるオブジェクトの情報」として保持

「掴んで移動」というのは具体的には、レーザーポインタで伸ばしている線の一定距離先の座標にGameObjectの座標を強制的に移動することで実現できる。

`_grabObject`: 掴んでいるオブジェクト
`_grabDistance `: 「一定距離先」の座標を求めるために「掴んだ時の自分とオブジェクトとの距離」を保存
`_grabOffset `: レーザーポインタが当たった座標と、掴んでいるオブジェクトの中心は違うので、その差を補正するための値
`_gravPrevFramePos `: これは「放り投げる」とき用の変数なのでいまは関係ない

```C#
			// トリガーボタンを押した時
			if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) {
				if (hitObj.name != "Plane") { // 地面は掴めない
					_grabObject = hitObj;
					_grabDistance = hitInfo.distance;
					_grabOffset = hitObj.transform.position - hitInfo.point; // ヒットした場所からオブジェクト中心までの距離
					_gravPrevFramePos = hitObj.transform.position;
				}
			}
```

### 3. 掴んでいるオブジェクトを移動させる

`pointerRay.GetPoint(_grabDistance)`で、レーザーポインタで伸ばしている線の一定距離先の座標を取得してそこにオブジェクトを移動させる。
何もしないと、空中に持ち上げていると重力加速度が溜まっていって離した時に一気に地面まで落ちるので`_grabObject.GetComponent<Rigidbody>().velocity = Vector3.zero;`で加速度をリセットし続けている。

```C#
			_grabObject.transform.position = pointerRay.GetPoint(_grabDistance) + _grabOffset;
			_grabObject.GetComponent<Rigidbody>().velocity = Vector3.zero;
```

### 4.オブジェクトを離す

掴んでいるオブジェクトをクリアして、`if (_grabObject != null) {...`の処理が行われないようにすればオブジェクトは離される。

```
			_grabObject = null;
```

### 5.オブジェクトを放り投げる

オブジェクトを離した瞬間に離したフレームのGameObject座標と、その1フレーム前のGameObject座標の差を加速度として与えてやれば、「GameObjectを放り投げる」ことができる。

毎フレーム`_gravPrevFramePos`に座標を保存しておきます。

```C#
		_gravPrevFramePos = _grabObject.transform.position;
```

オブジェクトを離した瞬間に、GameObject座標と、その1フレーム前のGameObject座標の差を`force`として取得してそれをRigidbodyの速度として設定します。(そのままだと力が弱いので適当な数を乗算して強い力にしています)

```C#
		// 掴んだオブジェクトを離す
		if (OVRInput.GetUp(OVRInput.Button.PrimaryIndexTrigger)) {
			// トリガーボタンを離した時
			Vector3 force = _grabObject.transform.position - _gravPrevFramePos;
			_grabObject.GetComponent<Rigidbody>().velocity = force * 30f;
			_grabObject = null;
		}
```