Как заставить игрока не отскакивать от поверхности?

Я хочу сделать так, чтобы персонаж не мог отлепится от пола. В моей игре игрок все время движется вправо, подобно игре rider. он движется по изгибающимися склонам, но часто отскакивает. Например: есть очень резкий спуск вниз и игрок на скорости просто едет в воздух и потом падает на спуск. Но в данном случае он должен не отрываясь от земли проехать по этому спуску. Но как это сделать?


Ответы (1 шт):

Автор решения: KOTlK

Не двигай игрока физикой, двигай вдоль поверхности.

using UnityEngine;

namespace MovementAlongSurface
{
    public interface IMovement
    {
        void Move(Vector3 direction);
    }
    
    public class AlongSurface : IMovement
    {
        private readonly float _speed;
        private readonly Rigidbody _origin;

        public AlongSurface(Rigidbody origin, float speed)
        {
            _origin = origin;
            _speed = speed;
        }

        public void Move(Vector3 direction)
        {
            var deltaTime = Time.deltaTime;
            var deltaMovement = _origin.position + direction * _speed * deltaTime;
            var normal = Normal(deltaMovement);
            if (normal == Vector3.zero) return;
            
            var targetDirection = TargetDirection(direction, normal);
            var targetPosition = _origin.position + targetDirection * _speed * deltaTime;

            _origin.MovePosition(targetPosition);
        }

        private Vector3 Normal(Vector3 startPosition)
        {
            var hits = new RaycastHit[1];
            return Physics.RaycastNonAlloc(
                startPosition, 
                Vector3.down, 
                hits) == 0 ? Vector3.zero : hits[0].normal;
        }
        
        private Vector3 TargetDirection(Vector3 direction, Vector3 normal)
        {
            return direction - Vector3.Dot(direction, normal) * normal;
        }
    }
}

Normal - делает рейкаст вниз и возвращает нормаль первой попавшейся поверхности либо возвращает 0, если снизу нет поверхности. TargetDirection - рассчитывает направление вдоль нормали с учетом направления движения. Что за формула и как ее получить объясняется здесь.

В Move:

  • Определяю позицию перед игроком, откуда буду делать рейкаст.
  • Рассчитываю нормаль.
  • Никуда не двигаемся, если впереди ничего нет.
  • Рассчитываю направление вдоль нормали.
  • Рассчитываю позицию, куда нужно переместить игрока.
  • Двигаю игрока.

Сам компонент игрока:

using UnityEngine;

namespace MovementAlongSurface
{
    [RequireComponent(typeof(Rigidbody))]
    public class Player : MonoBehaviour
    {
        [SerializeField] private float _speed = 10f;
        
        private IMovement _movement;
        
        private void Awake()
        {
            var rigidbody = GetComponent<Rigidbody>();
            rigidbody.isKinematic = true;
            _movement = new AlongSurface(
                rigidbody,
                _speed);
        }

        private void FixedUpdate()
        {
            var x = Input.GetAxis("Horizontal");
            var z = Input.GetAxis("Vertical");
            
            var direction = new Vector3(x, 0, z);
            if (direction == Vector3.zero) return;
            
            _movement.Move(new Vector3(x, 0, z));
        }
    }
}

Как сохранять постоянную высоту над поверхностью думай сам. Если нужна физика, то либо выключай/включай isKinematic по нужде, либо делай свою.

→ Ссылка