OculusTouch対応のゲームを作る手順:OculusTouchで扱う武器を実装する(弓)

[OculusTouch対応のゲームを作る手順目次はこちら](http://qiita.com/UnagiHuman/items/42e2059c04f55410bb1b)
#概要
[The Lab](https://www.youtube.com/watch?v=jT7Opu-AZyo)等のVRゲームでよくある弓の実装方法を手順を追って説明します。

#完成図

![bow.gif](https://qiita-image-store.s3.amazonaws.com/0/116884/c06ef6c3-f0ea-f7a6-45e7-a0e208075b1d.gif)

##弓に利用したアセットは以下の通りです。

[弓の本体](http://tf3dm.com/3d-model/green-arrow-bow-75051.html)
[矢](https://www.assetstore.unity3d.com/jp/#!/content/71680)

#考え方
弓に必要な機能を分解すると以下の通りになります。
1. 弓本体を拾って持つ機能
2. 弓を引いて矢を放つまでの一連の流れのコントロール
3. 弓のモデルのrigをコントロールして弓を引くアニメーションを行う
5. 矢の機能（当たって止まる、発射した後の矢じりが落ちる方向を向く）

それぞれ順を追って説明します。

#弓本体を拾って持つ機能
1は[以前の記事](http://qiita.com/UnagiHuman/items/7434a2daa5024fa86e8b)で紹介した物をキャッチするTouchObjectの機能がそのまま使えそうです。[弓の本体](http://tf3dm.com/3d-model/green-arrow-bow-75051.html)から手に入れたモデルに対して、RigitBodyとColliderとTouchObjectをアタッチします。TouchObjectをアタッチすればキャッチして持つ事が出来ます。
![bowall.PNG](https://qiita-image-store.s3.amazonaws.com/0/116884/f802da6f-f6d2-595b-40d6-0a7c35f2c603.png)

#弓を引いて矢を放つまでの一連の流れのコントロール
BowObjectというクラスがその働きをします。BowObjectクラスは後述で説明するクラスの全てをコントロールします。機能の一連の流れは下記のようになっています。
1. 弓をキャッチしたら弓を引けるようにする
2. キャッチした瞬間に矢をインスタンス化する
3. GrabObjectを掴むと矢を弓にセットする
4. GrabObjectを掴んだ手を引くと、矢を引くアニメーションが動作する
5. GrabObjectを離すと矢が発射される

![image.png](https://qiita-image-store.s3.amazonaws.com/0/116884/ebe548e6-60ed-5716-1e1d-afe87f2b20db.png)


```BowObject.cs
usi System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;

/// <summary>
/// 弓矢
/// </summary>
[RequireComponent (typeof(TouchObject))]
public class BowObject : MonoBehaviour
{

	/// <summary>
	/// 矢のPrefab
	/// </summary>
	[SerializeField] GameObject m_arrowPrefab;

	/// <summary>
	/// 矢を引き始める位置
	/// </summary>
	[SerializeField] GrabObject m_arrawGrabObject;

	/// <summary>
	/// 矢の場所
	/// </summary>
	[SerializeField] Transform	m_arrowRoot;

	BoolReactiveProperty m_isShotEnd = new BoolReactiveProperty (false);
	BowAnimationCont m_bowAnime;
	ArrowObject m_arrowObject;
	GameObject	m_arrawGameObject;
	TouchObject m_touch;

	void Start ()
	{
		m_touch = GetComponent<TouchObject> ();
		m_touch.IsCatch.Where (p => p).Subscribe (p => CreateArrow ());
		m_isShotEnd.Where (p => p).Delay (System.TimeSpan.FromMilliseconds (500)).Subscribe (p => CreateArrow ());
		m_touch.IsCatch.Where (p => !p).Subscribe (p => ResetGrab ());
		m_bowAnime = GetComponent<BowAnimationCont> ();
		m_arrawGrabObject.IsGrab.Where (p => p).Subscribe (p => ChangeColor (Color.red));
		m_arrawGrabObject.IsGrab.Where (p => !p).Subscribe (p => ChangeColor (Color.white));
	}


	/// <summary>
	/// Resets the grab.
	/// </summary>
	void ResetGrab ()
	{
		StopCoroutine (GetPowerCoroutine ());
		HandControllerManager.Instance.SetIsLockRaycaster (false, false);
		if (m_arrawGameObject != null) {
			Destroy (m_arrawGameObject);
			m_arrawGameObject = null;
		}
	}


	/// <summary>
	/// 矢を生成する
	/// </summary>
	void CreateArrow ()
	{
		m_isShotEnd.Value = false;
		var isLeftLock = m_touch.IsLockHand == TouchObject.LockHand.Left;
		HandControllerManager.Instance.SetIsLockRaycaster (isLeftLock, !isLeftLock);
		InstanceArrow (isLeftLock);
		//Debug.Log (bowLock);
		StartCoroutine (GetPowerCoroutine ());
	}


	/// <summary>
	/// インスタンス化
	/// </summary>
	/// <param name="isLeft">If set to <c>true</c> is left.</param>
	void InstanceArrow (bool isLeft)
	{
		if (m_arrawGameObject == null) {
			m_arrawGameObject = GameObject.Instantiate (m_arrowPrefab) as GameObject;
			SetParent (m_arrawGameObject.transform, HandControllerManager.Instance.GetHand (isLeft));
			m_arrawGameObject.transform.localRotation = Quaternion.Euler (new Vector3 (0, 0, 0));
			m_arrowObject = m_arrawGameObject.GetComponent<ArrowObject> ();
		}

	}


	void SetParent (Transform child, Transform parent)
	{
		child.parent = parent;
		child.localPosition = Vector3.zero;
		child.localScale = Vector3.one;
		child.localRotation = Quaternion.identity;
	}


	/// <summary>
	/// 矢を引いて放つ
	/// </summary>
	/// <returns>The power coroutine.</returns>
	IEnumerator GetPowerCoroutine ()
	{
		
		var wait = new WaitForEndOfFrame ();
		while (true) {
			if (m_arrawGrabObject.IsGrab.Value)
				break;
			yield return wait;
		}

		SetParent (m_arrawGameObject.transform, this.m_arrowRoot);

		m_arrawGrabObject.InGrabCB = MoveAllowCB;

		while (true) {
			if (!m_arrawGrabObject.IsGrab.Value) {
				break;
			}
				
			yield return wait;
		}
		var body = GetComponent<Collider> ();
		body.enabled = false;
		var grab = m_arrawGrabObject.gameObject.GetComponent<Collider> ();
		grab.enabled = false;
		m_arrawGrabObject.InGrabCB = null;
		m_arrowObject.Shot (new Vector3 (0, 0, GetPower ().magnitude * 1000));
		m_arrowObject.transform.parent = null;
		m_bowAnime.Reset ();
		m_arrawGameObject = null;
		yield return new WaitForSeconds (0.1f);
		grab.enabled = true;
		body.enabled = true;
		ResetGrab ();
		m_isShotEnd.Value = true;
	}


	/// <summary>
	/// 矢を放つ力を計算
	/// </summary>
	/// <returns>The power.</returns>
	Vector3 GetPower ()
	{
		var isLeft = m_touch.IsLeftHand;
		var bowGrabPos = Vector3.zero;
		var arrowHandPos = Vector3.zero;
		if (isLeft) {
			bowGrabPos = HandControllerManager.Instance.rightHand.transform.position;
			arrowHandPos = HandControllerManager.Instance.leftHand.transform.position;
		} else {
			bowGrabPos = HandControllerManager.Instance.leftHand.transform.position;
			arrowHandPos = HandControllerManager.Instance.rightHand.transform.position;
		}
		return bowGrabPos - arrowHandPos;
	}


	/// <summary>
	/// 矢を引く際に矢が動く時、マイフレーム実行されるCB
	/// </summary>
	/// <param name="arrowGrabPosition">Arrow grab position.</param>
	void MoveAllowCB (Vector3 arrowGrabPosition)
	{
		var isLeft = m_touch.IsLeftHand;
		var distance = (m_arrawGrabObject.transform.position - arrowGrabPosition).magnitude;
		m_arrowObject.transform.localPosition = new Vector3 (0, 0, -distance);
		m_bowAnime.DoYumiHiki (distance);
	}


	void ChangeColor (Color color)
	{
		foreach (var one in this.GetComponentsInChildren<Renderer>()) {
			one.materials [0].color = color;
		}
	}

}

```

#弓のモデルのrigをコントロールして弓を引くアニメーションを行う
弓矢の弓の部分にrigが入っていて、rigの座標を動かせば弓を引くアニメーションができる物が必要です。1から作るのも手ですが、フリーで使える[弓の本体](http://tf3dm.com/3d-model/green-arrow-bow-75051.html)があったので今回はこれを利用しています。
上記の画像のGreen_Arrow_Bow以下がアセットで、Armature以下がRigになります。弓のRigはStringになり、このY座標を動かす事で弓を引くアニメーションができます。

##Stringオブジェクトのy座標をコントロールするスクリプト

BowAnimationCont.csにてコントロールします。このクラスでは弓を引く距離を指定するとStringオブジェクトのy座標を動かします。

![bowString.PNG](https://qiita-image-store.s3.amazonaws.com/0/116884/3aaee1fa-243a-f972-4943-38173d75cb19.png)

```BowAnimationCont.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 弓をひくアニメーションをコントロール
/// </summary>
public class BowAnimationCont : MonoBehaviour
{
	/// <summary>
	/// アニメーションをするRig
	/// </summary>
	[SerializeField] Transform	m_targetBone;

	Vector3 m_localstartPos;

	void Start ()
	{
		m_localstartPos = m_targetBone.localPosition;
	}


	/// <summary>
	/// 弓を引く距離を指定する。距離はワールド座標系での距離
	/// </summary>
	/// <param name="worldDistance">World distance.</param>
	public void DoYumiHiki (float worldDistance)
	{
		m_targetBone.localPosition = 
			new Vector3 (
			m_targetBone.localPosition.x,
			m_localstartPos.y + worldDistance * (1f / m_targetBone.lossyScale.y), 
			m_targetBone.localPosition.z);
	}

	/// <summary>
	/// 弓の位置を元に戻す
	/// </summary>
	public void Reset ()
	{
		m_targetBone.transform.localPosition = m_localstartPos;
	}
}

```

#弓を引く
片方の手で弓をキャッチした後で、もう片方の手で弓を引く操作をするするイメージです。下記のGrabPositionオブジェクトにGrabObject.csとSphereColliderがアタッチされてますが、これを[以前の記事](http://qiita.com/UnagiHuman/items/7434a2daa5024fa86e8b)で作成したHandRaycasterで掴み、掴んだ後のOculusTouchコントローラの移動量を弓を引くためのアニメーションに利用したり、弓をひく動きと矢の移動とを連動させたりします。

![bowGrabObject.PNG](https://qiita-image-store.s3.amazonaws.com/0/116884/92d82754-6d2c-0c21-d8b7-0facbdf34199.png)

```GrabObject.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
using System;

/// <summary>
/// コントローラーで掴むObject
/// </summary>
public class GrabObject : EventBase
{

	/// <summary>
	/// 掴んだかどうか
	/// </summary>
	public BoolReactiveProperty IsGrab = new BoolReactiveProperty (false);

	/// <summary>
	/// 掴んでいる最中のCB。掴んだ手の位置が引数に入る
	/// </summary>
	/// <value>The m in grab C.</value>
	public Action<Vector3> InGrabCB {
		get;
		set;
	}
		

	// Use this for initialization
	void Start ()
	{
		IsGrab.Value = false;
	}


	public override void OnStartTouchEvent (HandRaycaster hitEvent)
	{
		base.OnStartTouchEvent (hitEvent);
		Debug.Log ("OnGrab");
		IsGrab.Value = true;
	}

	public override void OnInTouchEvent (HandRaycaster hitEvent)
	{
		base.OnInTouchEvent (hitEvent);

		if (HandControllerManager.Instance.IsGetDownIndexTrigger (hitEvent.isLeftHand)) {
		} else if (HandControllerManager.Instance.IsGetHandTrigger (hitEvent.isLeftHand)) {
			InGrabCB (hitEvent.transform.position);
		}
	}


	public override void OnEndTouchEvent (HandRaycaster hitEvent)
	{
		base.OnEndTouchEvent (hitEvent);

		IsGrab.Value = false;
	
	}


}

```

#矢の機能（当たって止まる、発射した後の矢じりが落ちる方向を向く）

矢の本体の実装です。下記のコメントを見れば大体機能が分かると思います。

```ArrowObject.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UniRx;
using UniRx.Triggers;

/// <summary>
/// 矢
/// </summary>
public class ArrowObject : MonoBehaviour
{
	Collider m_body;

	Rigidbody m_rigit;

	void Awake ()
	{
		
		m_rigit = GetComponent<Rigidbody> ();
		m_rigit.isKinematic = true;
		m_rigit.useGravity = false;
		m_body = GetComponent<Collider> ();
		m_body.enabled = false;

		//何かに衝突したら遅れて削除される
		this.OnCollisionEnterAsObservable ()
			.Delay (TimeSpan.FromMilliseconds (3000.0))
			.Subscribe (p => Destroy (gameObject)).AddTo (gameObject);

		//何かに刺さったら止まる
		this.OnCollisionEnterAsObservable ()
			.Subscribe (p => Stop (p.transform));

	}


	/// <summary>
	/// 矢を放つ
	/// </summary>
	/// <param name="power">Power.</param>
	public void Shot (Vector3 power)
	{
		Debug.Log ("Arrow Shot");
		m_body.enabled = true;
		m_rigit.isKinematic = false;
		m_rigit.useGravity = true;
		m_rigit.AddRelativeForce (power);
		this.FixedUpdateAsObservable ().Subscribe (p => FallArrowTask ()).AddTo (gameObject);
	}


	/// <summary>
	/// 矢が刺さって止まる
	/// </summary>
	/// <param name="target">Target.</param>
	void Stop (Transform target)
	{
		m_body.enabled = false;
		var ver = m_rigit.velocity;
		m_rigit.velocity = Vector3.zero;
		m_rigit.useGravity = false;
		m_rigit.isKinematic = true;
		//少々突き刺ささる
		m_rigit.transform.position += ver * Time.fixedDeltaTime * 2;
		this.transform.transform.SetParent (target);
	}


	/// <summary>
	/// 矢じりを落ちる方向に向ける
	/// </summary>
	void FallArrowTask ()
	{
		if (!m_rigit.isKinematic) {
			var ver = m_rigit.velocity;
			var falldir = ver.normalized;
			//落ちる方向を向くようにする
			m_rigit.MoveRotation (Quaternion.LookRotation (falldir));
		}
	
	}
}

```
#まとめ
長かったですが、以上が弓矢の実装の説明になります。あとはSEとOculusTouchの振動を加えればもっと良くなると思います。