LoginSignup
9
6

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-02-13

OculusTouch対応のゲームを作る手順目次はこちら

概要

The Lab等のVRゲームでよくある弓の実装方法を手順を追って説明します。

完成図

bow.gif

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

弓の本体

考え方

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

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

弓本体を拾って持つ機能

1は以前の記事で紹介した物をキャッチするTouchObjectの機能がそのまま使えそうです。弓の本体から手に入れたモデルに対して、RigitBodyとColliderとTouchObjectをアタッチします。TouchObjectをアタッチすればキャッチして持つ事が出来ます。
bowall.PNG

弓を引いて矢を放つまでの一連の流れのコントロール

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

image.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から作るのも手ですが、フリーで使える弓の本体があったので今回はこれを利用しています。
上記の画像のGreen_Arrow_Bow以下がアセットで、Armature以下がRigになります。弓のRigはStringになり、このY座標を動かす事で弓を引くアニメーションができます。

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

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

bowString.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がアタッチされてますが、これを以前の記事で作成したHandRaycasterで掴み、掴んだ後のOculusTouchコントローラの移動量を弓を引くためのアニメーションに利用したり、弓をひく動きと矢の移動とを連動させたりします。

bowGrabObject.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の振動を加えればもっと良くなると思います。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6