掴んで、投げる

※この記事は『ひとりVRゲーム作ろうな Advent Calendar 2016』の3日目の記事です。

昨日の記事が完全に間に合わせなので、昨日の部分を改めて…(今日作業しようと思ったら一日中出ていたのでできなかったマン)

初日に挙げた、

1.雪面(地面)に触れてコントローラを握る(トリガーを引く)と雪玉を持てる

2.それを投げる

を実現するところから始めました。

ざっくばらんにゲームオブジェクトを配置

なんとなくでゲームオブジェクトを配置。


・地面となるPlane(+Collider)
・雪玉となるSphere(+Collider,Rigidbody)
・SteamVRアセットに含まれている[CameraRig]
※カメラが競合するのでもともとあるカメラは削除しておきます。

掴んで投げる

とりあえず投げれないとダメなので、そこから。
SteamVRアセットに付属していたSteamVR_TestThrow.csを元に、握れるオブジェクト&それを握るスクリプトを追加。

using UnityEngine;
using System.Collections;

/// <summary>
/// アタッチ可能オブジェクト.
/// FixedJointを介してオブジェクト間の接続をサポート.
/// </summary>
public class AttachableObject : MonoBehaviour {

    private FixedJoint _joint = null;

    /// <summary>
    /// アタッチ.
    /// </summary>
    /// <param name="to">接続先のリジッドボディ.</param>
    public void Attach(Rigidbody to)
    {
        Debug.Assert(null != to);

        if (null == _joint)
        {
            _joint = gameObject.AddComponent<FixedJoint>();
        }
        Debug.Assert(null != _joint);

        _joint.connectedBody = to;
    }
    /// <summary>
    /// デタッチ.
    /// </summary>
    public void Detach()
    {
        if (null != _joint)
        {
            Object.DestroyImmediate(_joint);
            _joint = null;
        }
    }
}

using UnityEngine;
using System.Collections;


public class ThrowParam
{
    public Vector3 velocity;
    public Vector3 angularVelocity;

    public ThrowParam()
    {
        velocity = Vector3.zero;
        angularVelocity = Vector3.zero;
    }
}

/// <summary>
/// 投げられる用オブジェクト.
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class ThrowableObject : AttachableObject {

    private Rigidbody _rigid = null;

    /// <summary>
    /// 投射.
    /// </summary>
    /// <param name="param">投射パラメータ.</param>
    public void Throw(ThrowParam param)
    {
        if (null == _rigid)
        {
            return;
        }
        _rigid.velocity = param.velocity;
        _rigid.angularVelocity = param.angularVelocity;

        _rigid.maxAngularVelocity = _rigid.angularVelocity.magnitude;
    }

	// Use this for initialization
	void Start ()
    {
        _rigid = gameObject.GetComponent<Rigidbody>();
        Debug.Assert(null != _rigid);
	}
	
	// Update is called once per frame
	void Update () {
	    
	}
}

掴む側のスクリプト
SteamVR_Controller.DeivceはサンプルではUpdate()で取ってたけど今のところ問題になっていないのでStart()で取得したきりにしてます。
この時点ではデバッグ用にとりあえず球を生み出す処理も含めてます。

using UnityEngine;
using System.Collections;

public class Catch : MonoBehaviour {

    [SerializeField]
    private SteamVR_TrackedObject _tracker = null;

    [SerializeField, HideInInspector]
    private SteamVR_Controller.Device _controller = null;

    [SerializeField]
    private Rigidbody _attachPoint = null;

    private Thrower _thrower = null;

    public ThrowableObject TakingObject
    {
        get { return (null == _thrower) ? null : _thrower.Taking; }
    }
    public Vector3 Velocity
    {
        get { return (null == _controller) ? Vector3.zero : _controller.velocity; }
    }
    public SteamVR_Controller.Device Controller
    {
        get { return _controller; }
    }

    [SerializeField]
    private ThrowableObject _spawner = null;
    [SerializeField]
    private float _throwPowerScale = 1.0f;

    // Use this for initialization
    void Start()
    {
        if (null == _tracker)
        {
            _tracker = GetComponent<SteamVR_TrackedObject>();
        }
        if (null != _tracker)
        {
            _controller = SteamVR_Controller.Input((int)_tracker.index);
        }
        if (null == _thrower)
        {
            _thrower = new Thrower();
        }
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        if (_controller.GetTouchDown(SteamVR_Controller.ButtonMask.Touchpad))
        {
            if (IsHoldable())
            {
                ThrowableObject obj = GameObject.Instantiate(_spawner);
                obj.transform.position = _attachPoint.position;
                Take(obj);
            }
        }

        if (!IsHolding())
        {
            return;
        }
        if (_controller.GetTouchUp(SteamVR_Controller.ButtonMask.Trigger))
        {
            _thrower.Release(_tracker, _controller, _throwPowerScale);
        }
    }

    void OnTriggerStay(Collider other)
    {
        if (!IsHoldable())
        {
            return;
        }

        if (_controller.GetPressDown(SteamVR_Controller.ButtonMask.Trigger))
        {
            ThrowableObject throwable = other.gameObject.GetComponent<ThrowableObject>();
            if (null != throwable)
            {
                Take(throwable);
            }
        }
    }
    public bool IsHoldable()
    {
        return !IsHolding() 
            && (null != _thrower) 
            && (null != _controller) 
            && (null != _tracker);
    }

    public bool IsHolding()
    {
        return (null != _thrower) 
            && _thrower.HasObject();
    }
    public void Take(ThrowableObject throwable, bool isFixedPosition = false)
    {
        if (null == throwable)
        {
            return;
        }
        Debug.Assert(IsHoldable());
        Debug.Assert(!IsHolding());
        if (isFixedPosition)
        {
            throwable.transform.position = _attachPoint.position;
        }
        
        _thrower.Take(throwable, _attachPoint);
    }
}

で?


こうすることでゲーム中にSteamVRコントローラを通じて球を掴むことができるようになりました。
タイミングよくトリガーを放すと球を投げることが可能です。

雪面に触れて…の部分は勿体ぶるほどのことはしてませんが明日に…もう眠くてしんどい!