雪玉のインタラクション

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

…投稿日ちがくね?という話はさておき…
仏の顔も3度までやし!

雪感も増えて少し満足してしまったところもありますが、
雪玉に対してのインタラクションはまだ足りないのでこれを付けていきます。
主に以下の3つ。

  1. 地面に触れながらトリガーを引くと雪玉を掴める
  2. 雪玉を掴みながら地面を擦るように振ると雪玉が大きくなる
  3. 雪玉がぶつかったときのリアクション

雪玉を作り出す

接地判定が必要なのでとりあえずBox Colliderを地面にアタッチしておきます。


タグ参照で衝突判定を弾くつもりでいるので、タグを"Snow"として設定しておきます。

コントローラに以前取り上げたCatchと以下のBall Craftをアタッチすることで、
コントローラと地面との間でやり取りを行えます。

using UnityEngine;
using System.Collections;

public class BallCraft : MonoBehaviour {

    /// <summary>
    /// オブジェクトを握る手
    /// </summary>
    [SerializeField]
    private Catch _catcher = null;

    /// <summary>
    /// 握るオブジェクト(生成対象)
    /// </summary>
    [SerializeField]
    private ThrowableObject _ball = null;
    /// <summary>
    /// オブジェクトを大きくする際の閾値となる加速度
    /// </summary>
    [SerializeField]
    private float _makableVelocityMagnitude = 2.0f;
    /// <summary>
    /// 閾値を満たしたときの増加スケール[scale/s]
    /// </summary>
    [SerializeField]
    private float _addMakingScale = 0.1f;

	// Use this for initialization
	void Start () {
        Debug.Assert(null != _catcher);
        Debug.Assert(null != _ball);
	}
	
    void OnCollisionStay(Collision other)
    {
        if (IsAccept(other.gameObject))
        {
            OnCollidingProcess();
        }
    }
    void OnTriggerStay(Collider other)
    {
        if (IsAccept(other.gameObject))
        {
            OnCollidingProcess();
        }
    }

    /// <summary>
    /// 衝突時の処理を行えるか
    /// </summary>
    /// <param name="obj">衝突したオブジェクト</param>
    /// <returns>処理の可否</returns>
    private bool IsAccept(GameObject obj)
    {
        if ("Snow" != obj.gameObject.tag)
        {
            return false;
        }
        return true;
    }

    /// <summary>
    /// 衝突時の処理
    /// </summary>
    private void OnCollidingProcess()
    {
        if (_catcher.IsHolding())
        {
            Making(_catcher);
        }
        else if (_catcher.IsHoldable())
        {
            if (_catcher.Controller.GetPressDown(SteamVR_Controller.ButtonMask.Trigger))
            {
                Spawn(_catcher);
            }
        }
    }

    /// <summary>
    /// オブジェクトの生成および握り
    /// </summary>
    /// <param name="catcher">握る手</param>
    private void Spawn(Catch catcher)
    {
        Debug.Assert(null != catcher);

        ThrowableObject instance = GameObject.Instantiate(_ball);
        
        catcher.Take(instance, true);
    }
    /// <summary>
    /// 握っているオブジェクトを大きくする
    /// </summary>
    /// <param name="catcher">握る手</param>
    private void Making(Catch catcher)
    {
        Debug.Assert(null != catcher);

        ThrowableObject taking = catcher.TakingObject;
        if (null != taking)
        {
            if (catcher.Velocity.sqrMagnitude >= _makableVelocityMagnitude * _makableVelocityMagnitude)
            {
                taking.transform.localScale += Vector3.one * _addMakingScale * Time.deltaTime;
            }
        }
    }
}



こんな感じで掴めます。

雪玉が大きくなるのはこんな感じ。

最初ポッと出てくるのも違和感があるかもしれないですね。もっと小さいスケールから振って大きくするでもいいのかもなー。
大きくしてるときは何となく小さい雪しぶき的なパーティクルがあると良い気がしてきました。

衝突時のインタラクション

やはり雪玉なので割れるようなインタラクションが必要。
何となくそれっぽい?程度のパーティクルを作りました。
雪玉と同じマテリアルを使ってSphereを真上に散らすような感じのものですね。
スクショが無いので後ほど…

雪玉に以下のクラスの派生クラスを作成します。

using UnityEngine;
using System.Collections;

public abstract class BreakableObject : MonoBehaviour {

    /// <summary>
    /// 非破壊フラグ.
    /// 何かに衝突しても破壊されなくなります.
    /// IsUnbreakable == trueの場合、OnBreakEvent()はコールされません.
    /// </summary>
    [SerializeField]
    private bool _isUnbreakable = false;
    public bool IsUnbreakable
    {
        set
        {
            _isUnbreakable = value;
            OnChangeUnbreakableFlag(_isUnbreakable);
        }
        get { return _isUnbreakable; }
    }

    /// <summary>
    /// 非破壊フラグ更新時のイベント
    /// </summary>
    /// <param name="isUnbreakable">更新後の非破壊フラグの値</param>
    protected abstract void OnChangeUnbreakableFlag(bool isUnbreakable);
    /// <summary>
    /// オブジェクト破壊時のイベント
    /// </summary>
    /// <param name="obj">衝突したオブジェクト</param>
    /// <param name="contacts">衝突情報</param>
    /// <returns>自身のオブジェクトの破壊の有無</returns>
    protected abstract bool OnBreakEvent(GameObject obj, ContactPoint[] contacts = null);
    
    void OnCollisionEnter(Collision other)
    {
        Debug.Assert(null != other);
        OnBreak(other.gameObject, other.contacts);
    }
    void OnCollisionStay(Collision other)
    {
        Debug.Assert(null != other);
        OnBreak(other.gameObject, other.contacts);
    }
    void OnCollisionExit(Collision other)
    {
        Debug.Assert(null != other);
        OnBreak(other.gameObject, other.contacts);
    }

    void OnTriggerEnter(Collider other)
    {
        Debug.Assert(null != other);
        OnBreak(other.gameObject);
    }
    void OnTriggerStay(Collider other)
    {
        Debug.Assert(null != other);
        OnBreak(other.gameObject);
    }
    void OnTriggerExit(Collider other)
    {
        Debug.Assert(null != other);
        OnBreak(other.gameObject);
    }
    
    private bool OnBreak(GameObject obj, ContactPoint[] contacts = null)
    {
        Debug.Assert(null != obj);
        if (IsUnbreakable)
        {
            return false;
        }
        if (!OnBreakEvent(obj, contacts))
        {
            return false;
        }
        Destroy();
        return true;
    }

    private void Destroy()
    {
        this.gameObject.SetActive(false);
        GameObject.Destroy(this.gameObject);
    }
}


派生先で衝突点からパーティクルを発生させる処理を追加します。
衝突コードは何となくな感じ。計算式がアレなのでブラッシュ段階で手入れですかね…

        // 破壊時のパーティクル
        if (null != contacts && null != _crashParticle)
        {
            GameObject particle = GameObject.Instantiate(_crashParticle);
            Rigidbody rigid = contacts[0].thisCollider.GetComponent<Rigidbody>();
            
            particle.transform.position = contacts[0].point;
            particle.transform.localScale = this.transform.localScale;
            particle.transform.localRotation = Quaternion.LookRotation(Vector3.Reflect(contacts[0].normal, rigid.velocity));

            ParticleSystem system = particle.GetComponentInChildren<ParticleSystem>();
            if (null != system)
            {
                var mainModule = system.main;
                mainModule.startSpeed = _crashVelocityCoef * rigid.velocity.magnitude / particle.transform.localScale.x;
                
                var velocity = system.velocityOverLifetime;
                Vector3 r = Vector3.Reflect(rigid.velocity, contacts[0].normal);
                r = r.normalized / particle.transform.localScale.x;
                velocity.x = r.x;
                velocity.y = r.y;
                velocity.z = r.z;
            }
        }

こんな具合になりました。


フィールドとしての体裁はなんとなく整った気がするので、
そろそろかわいい女の子にご登場頂きたいですね…
モーションどうしよう…(白目)