見出し画像

Unityゲーム役立ち小ネタスクリプト6:そこそこな性能の移動床

なんか苦戦した3Dプラットフォーム=移動床の作り方です。

移動床の上で通常アクション可能、跳ねても一瞬なら置いてかれない

1.キャラやオブジェクトの足元にセンサーコンポーネントCharaFootDetector .csをつけます。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

    /// <summary>
    /// トリガーで足元を検知する。
    /// 移動床対策。
    /// </summary>
    public class CharaFootDetector : MonoBehaviour
    {
        [SerializeField, Header("Rootオブジェクト")]
        GameObject RootObjext;
        [SerializeField, Header("検知レイヤーマスク")]
        LayerMask layerMask;
        [SerializeField, Header("検知タグ(複数対応)")]
        string[] FootTag;
        List<string> tagDetect = new List<string>();

        [SerializeField, Header("otherObjextの_moveVector"), Tooltip("_moveVectorを検出したら移動量を取得する")]
        MoveVector _moveVector;

        [SerializeField, Header("確認用:移動量"), Tooltip("移動量")]
        Vector3 rvel;

        [SerializeField, Header("確認用:オブジェクト")]
        GameObject otherObjext;
        [SerializeField, Header("確認用:オブジェクトのタグ")]
        string othertag;

        float rayposoffset = 0.3f;
        float ColExitTimer = 0.1f;
        float ColExitCount;
        bool ColExit;

        public void Awake()
        {
            for (int i = 0; i < FootTag.Length; i++)
            {
                tagDetect.Add(FootTag[i]);
            }
        }


        private void FixedUpdate()
        {
            //レイキャストをセット
            RaycastHit hit;
            Vector3 RayPos = transform.position;
            RayPos.y += rayposoffset;

            Ray ray = new Ray(RayPos, transform.up*-1);
            Debug.DrawRay(RayPos, transform.up * -rayposoffset, Color.red, 0.2f);
            
            //レイキャストが特定のレイヤー・タグのオブジェクトにヒットしたら、MoveVectorコンポーネントを取得する
            //ヒットしなかったら、コライダーヒットせずのカウントを開始する
            if (Physics.Raycast(ray,out hit, rayposoffset+0.1f, layerMask))
            {
                otherObjext = hit.collider.gameObject;
                othertag = otherObjext.tag;
                if (tagDetect.Contains(othertag))
                {
                    _moveVector = otherObjext.GetComponent<MoveVector>();
                    ColNoHit = false; ColExitCount = 0;
                }
                else
                {
                    ColNoHit = true;
                }
            }
            else
            {
                ColNoHit = true;
            }

            //移動床から一定時間外れたら_moveVectorを消去
            if (ColExit)
            {
                ColExitCount += Time.deltaTime;
                if (ColExitCount > ColExitTimer)
                {
                    ColExit = false; ColExitCount = 0;
                    _moveVector = null;
                    rvel = Vector3.zero;
                }
            }
            if (_moveVector != null)
            {
                rvel = _moveVector.GetMoveVector();
            }
          
            if ((rvel != Vector3.zero))
            {
                if (RootObjext != null)
                {
                    Vector3 pa = RootObjext.transform.position;
                    pa += rvel;
                    RootObjext.transform.position = pa;
                }
            }
        }
    }

動かしたいオブジェクト=Rootオブジェクトと、移動床を特定するためのタグとレイヤーマスクを指定します。それ以外の項目は確認用で記入不要です

コライダーは複数タグで判別する仕様です

2.移動床には、タグとレイヤーを移動床と特定できるものにセットし、コライダーと移動床コンポーネントMoveVector をつけます。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

    /// <summary>
    /// 移動量.回転量を記録する。
    /// </summary>
    public class MoveVector : MonoBehaviour
    {
        [SerializeField, Header(""), Tooltip("")]
        bool UseVelocity=true;

        [SerializeField, Header("移動量"), Tooltip("移動量")]
        Vector3 _velocity;

        Vector3 prepos;

        [SerializeField, Header("インターバル"), Tooltip("")]
        float vqIntervalTimer=0f;
        float vqCount;

        void Awake()
        {
            Resetvq();
        }
        
        private void FixedUpdate()
        {
            if(UseVelocity)
            {
                SetVector();   
            }
        }

        void Resetvq()
        {
            prepos = transform.position;
            _velocity = Vector3.zero;
        }

        void SetVector()
        {
            _velocity = transform.position - prepos;

            prepos = transform.position;
        }

        public Vector3 GetMoveVector()
        {
            if (UseVelocity) {  return _velocity; }
            return Vector3.zero;
        }

    }


それと移動床を動かすためのコンポーネント…DoTweenとかスプラインとかを別途用意し、セットします。(移動床にrigidbodyがあると、addforceなどで動かさない限りコライダーが吹っ飛ぶので使用不可です)


CharaFootDetector とMoveVector の概要ですが、

1.MoveVectorは毎フレーム、前フレームの座標を控えておく
2.前フレームの座標と現座標の差から移動ベクトルを算出する
3.CharaFootDetector は移動床のコライダーに触ったらMoveVector の持つベクトルを取得して、Rootの座標を操作して位置を変更する(回転ベクトルも同様に取得・操作できますが計算が数倍難しいので今回はなしです)
4.キャラがジャンプするなどしてセンサーが移動床から外れて一定時間経過したら(0.1秒ほど)、移動床の参照をやめる。

という感じです。
移動床が回転しなければこれでいけます。回転する場合でも、移動床を小さく複数敷き詰めるようにすれば、回転時の位置のズレを軽減できます。

これらのオブジェクトの移動量をはかりトランスフォームをいじる処理はすべてFixedUpdateで行い、カメラだけFixedLastUpdateにします。通常のupdateだと、動きと描画の同期が合わずガタガタした動きになります。
デフォだと30fpsになるので、60フレームにするにはApplication.targetFrameRate = 60;をシーン上にあるゲームマネージャー系のコンポーネントとかに書き加えます。

ここに至るまで、移動床を認識しなかったり、ベクトルを得ても移動が足りなかったりする試行錯誤がありました。
理由はこう

1.コライダー検出が他のコライダーに吸われてた
最初、レイキャストでなくトリガーで調べてたのが失敗でした。
キャラに使ってたラグドールアセットの足コライダーがトリガーの検知に引っかかって、移動床の検知が妨げられてました。
足のコライダーと足下トリガーのサイズを小さくしてできるだけ衝突を回避して対処できなくもないですが、コライダーを持つ他のオブジェクトが足元に飛び込んでくるとだめです。レイキャスト一択か

(トリガーやレイキャストは複数のオブジェクトが同時に接触した時、個別に検出できないのかな?)

2.キャラコンのトランスフォームが適切にいじれてない
センサーコンポーネントはRootのトランスフォームを操作して移動床の追随を実現しますが、ナビメッシュエージェントやサード製キャラコンを使ってるなどの状況によってはそれができません。特に回転はキャラクターコントローラを使ってると、キャラクターコントローラ側で回転させる必要があります。キャラの回転はTPSカメラも制御してるはずなので、それを考慮する必要もあります。
rigidbodyで動かすタイプだったら、もう計算考えたくない
キャラに揺れもの=ラグドールやクロスシミュ系アセットを使ってたら、それらが吹っ飛ぶ対策も要るかもでしょう。

3.ド早いのは無理
ソニックのゲームに出てくるぐらいの超スピード移動床だと、移動ベクトルを計算しきれないのか振り落とされる可能性があります。その前に揺れものが先に死にます


この記事が気に入ったらサポートをしてみませんか?