プログラミングとかブログ

Unity/C#/SRPGStudio/RPGツクールMVの情報とかその他気になったことを調べて書きます。

2Dコライダの境目にひっかかる現象への暫定対処

公式のUnityちゃん2Dサンプルプロジェクトで、コライダの境目付近でジャンプし、そのまま着地した直後に境目方向へ移動するとなぜかひっかかり進めません。(自PCだけ?)
f:id:shirakamisauto:20150529112513g:plain

ジャンプ着地時にめりこんだあと、位置がコライダ上面に戻りきらないために起きていると思っているのですが、transformのY位置が変わっていないときもあるので、確証はありません。
まあ頻繁に出るものでもないので問題ない気もしますが、一応どうにかしたかったので考えてみました。
ちなみに根本的な解決にはなってません。
めり込み具合をいじれればいいんですが・・・。*1

UnityChan2DController.cs

using System.Collections;
using UnityEngine;
[RequireComponent(typeof(Animator), typeof(Rigidbody2D), typeof(BoxCollider2D))]
public class UnityChan2DController : MonoBehaviour
{
    public float maxSpeed = 10f;
    public float jumpPower = 20f;
    ~中略~

    void Reset()
    {
        Awake();

        // UnityChan2DController
        maxSpeed = 10f;
        jumpPower = 20;
        ~中略~
    }

    void Awake()
    {
        m_animator = GetComponent<Animator>();
        m_boxcollier2D = GetComponent<BoxCollider2D>();
        m_rigidbody2D = GetComponent<Rigidbody2D>();
    }

    /// 直前のFixedUpdateフレームでの位置
    Vector2 _preFramePos = new Vector2();

    /// 直前のFixedUpdateフレームで移動操作をしたかどうか
    bool _isMovePreFlame = false;

    /// 床の境目にひっかかったときに上に押し上げる移動量
    public float _deltaUpwardPos = 0.03f;

    /// 前のフレームとのX変位限界 移動量がこの範囲内ならひっかかってるとみなす
    public float _deltaRangeX = 0.001f;

    /// Updateでジャンプ入力を受け付けたフラグ FixedUpdateでこのフラグを見てジャンプ
    bool _jumpFlag = false;

    /// 十字キーによる移動量
    float _deltaX = 0;

    void Update()
    {
        if (m_state != State.Damaged)
        {
            _deltaX = Input.GetAxisRaw("Horizontal");
            _jumpFlag = Input.GetButton("Jump");
        }
    }

    void FixedUpdate()
    {
        Vector2 pos = transform.position;
        Vector2 groundCheck = new Vector2(pos.x, pos.y - (m_centerY * transform.localScale.y));
        Vector2 groundArea = new Vector2(m_boxcollier2D.size.x * 0.49f, 0.05f);

        m_isGround = Physics2D.OverlapArea(groundCheck + groundArea, groundCheck - groundArea, whatIsGround);
        m_animator.SetBool("isGround", m_isGround);

        Move(_deltaX, _jumpFlag);
    }

    void Move(float move, bool jump)
    {
        //地上にいるとき
        //前のフレームで移動操作を行っており、かつ前のフレームとのX変位がないorほぼない場合に
        //目の前に障害物があるか確認し、ないならばY位置を微上昇
        //要するにめりこみ段差にひっかかったらちょっと上へ移動させる
        if (_isMovePreFlame && Mathf.Abs(transform.position.x - _preFramePos.x) < _deltaRangeX && m_isGround)
        {
            var pos = transform.position;
            var direction = Mathf.Cos(transform.rotation.y);
            var lineEndPos = new Vector2(pos.x + (m_boxcollier2D.size.x / 2 + 0.1f) * direction, pos.y);

            //進行方向のプレイヤー先端で壁ブロック検知 
            bool isHitForward = Physics2D.Linecast(pos,lineEndPos,whatIsGround);
            
            //めりこみによる段差で床にひっかかったら微上昇 上昇量を増やすとどこで引っかかったかわかりやすい
            if (!isHitForward)
                transform.position = new Vector3(pos.x, pos.y + _deltaUpwardPos, pos.z);
        }
        _preFramePos = transform.position;
        _isMovePreFlame = false;

        if (Mathf.Abs(move) > 0)
        {
            //向き反転
            Quaternion rot = transform.rotation;
            transform.rotation = Quaternion.Euler(rot.x, Mathf.Sign(move) == 1 ? 0 : 180, rot.z);

            _isMovePreFlame = true;
        }
        
        m_animator.SetFloat("Horizontal", move);
        m_animator.SetFloat("Vertical", m_rigidbody2D.velocity.y);
        m_animator.SetBool("isGround", m_isGround);

        if (jump && m_isGround)
        {
            jump = false;
            m_animator.SetTrigger("Jump");
            SendMessage("Jump", SendMessageOptions.DontRequireReceiver);
            m_rigidbody2D.velocity = new Vector2(move * maxSpeed, jumpPower);
        }

        m_rigidbody2D.velocity = new Vector2(move * maxSpeed, m_rigidbody2D.velocity.y);
    }

    void OnTriggerStay2D(Collider2D other)
    {
        ~以下略~
}

やってることはひっかかったら持ち上げてるだけです。
詳しく言うと、前フレームからの移動量がごく少ない(=ひっかかってる)場合、かつ壁衝突によって移動量が小さいとき以外の場合にほんの少し持ち上げるという処理です。
また、元のコードはUpdate内で移動処理を行っていましたが、うまくいかなかったので、FixedUpdate内でやっています。

根本的に解決するならコライダの境目を作らないように、長ーいコライダにすればいいんですが、地面の間の落下床などどうしても境目ができる場合もあるので、完璧ではないですね。

ユニティちゃんライセンス

このコンテンツは、『ユニティちゃんライセンス』で提供されています

*1:Min Penetration For Penaltyってのをいじってみたけど変わらなかった