Создание контроллера FPS на базе RigidbodyC#

Место общения программистов C#
Ответить
Anonymous
 Создание контроллера FPS на базе Rigidbody

Сообщение Anonymous »

Я пытаюсь создать свой собственный контроллер FPS на основе Rigidbody с включенной функцией «isKinematic». Я также пытаюсь воспроизвести ту же механику контроллера, что и в таких играх, как Amnesia, Penumbra, Portal, Half-Life и т. д.
Дело в том, что я не понимаю, как они работают, и мои алгоритмы не работали. Если кто-то делал что-то подобное и успешно создал контроллер, расскажите, пожалуйста, как вы реализовали свой контроллер.
Теперь немного о том, как я пытался это реализовать сам:
1. У меня есть две системы — конечный автомат игрока и сервис «Мотор игрока». В конечном автомате я просто вызываю методы сервиса.

Код: Выделить всё

// Grounded state
public override void FixedTick()
{
base.FixedTick();

Vector3 inputMove = _inputReader.Move;
(Vector3 forward, Vector3 right) = _motor.GetPlayerDirs();

Vector3 desiredMove = forward * inputMove.y + right * inputMove.x;

_motor.Move(desiredMove * SPEED * Time.fixedDeltaTime);

if (!_motor.IsGrounded || _inputReader.IsJumping)
{
_stateMachine.SwitchState
();
return;
}
}

// Airborne state
public override void FixedTick()
{
base.FixedTick();

_verticalVelocity += GRAVITY * Time.fixedDeltaTime;

Vector3 inputMove = _inputReader.Move;
(Vector3 forward, Vector3 right) = _motor.GetPlayerDirs();

Vector3 offsetMove = (forward * inputMove.y + right * inputMove.x).normalized;

Vector3 downMove = Vector3.down;

_motor.Move(downMove * _verticalVelocity * Time.fixedDeltaTime + offsetMove * 2f * Time.fixedDeltaTime);

if (_motor.IsGrounded)
{
_stateMachine.SwitchState();
return;
}
}
2. В сервисе все гораздо сложнее. В общих чертах я проверяю, есть ли земля под ногами (это необходимо для переключения состояний в конечном автомате), а затем редактирую вектор положения игрока, чтобы он не залезал на поверхности с уклоном больше определенного градуса, а притягивался к земле, если расстояние между землей и игроком приемлемое.

Код: Выделить всё

public void Move(Vector3 desiredMove)
    {
        CheckGround();

        Vector3 position = _playerRigidbody.position;
        position = MoveWithSlide(position, desiredMove);
        position = SnapToGround(position);

        _playerRigidbody.MovePosition(position);
    }
3. Землю проверяю с помощью SphereCast — здесь нет ничего сложного, смотрите код ниже.

Код: Выделить всё

private void CheckGround()
    {
        float halfHeight = _playerCollider.height * 0.5f;
        float radius = _playerCollider.radius;
        float dist = halfHeight - radius + PROBE_DOWN;

        Vector3 center = _playerCollider.transform.TransformPoint(_playerCollider.center);

        Ray sphereRay = new Ray(center, Vector3.down);
        bool isSupported = Physics.SphereCast(sphereRay, radius, dist,
                                              _worldMask, QTI);

        _isGrounded = isSupported;
    }
4. С обнаружением столкновений сложнее: здесь я проверяю, есть ли перед объектом уклон, и если есть, то двигаемся к точке столкновения с ним. Если уклон имеет нормальную для нас степень, мы также проверяем, насколько далеко мы можем по нему продвинуться, иначе рассматриваем его как стену.

Код: Выделить всё

private Vector3 MoveWithSlide(Vector3 position, Vector3 desiredMove)
    {
        float desiredDist = desiredMove.magnitude;
        Vector3 desiredDir = desiredMove / desiredDist;

        GetCapsulePoints(position, out Vector3 p1, out Vector3 p2, out float radius);

        if(!Physics.CapsuleCast(p1, p2, radius, desiredDir,
                                out RaycastHit hit,
                                desiredDist + SKIN,
                                _worldMask, QTI))
        {
            return position + desiredMove;
        }

        float travel = Mathf.Max(0f, hit.distance - SKIN);
        position += desiredDir * travel;

        Vector3 left = desiredMove - desiredDir * travel;

        Vector3 slide;

        if(CheckSlopeAngle(hit.normal))
        {
            slide = Vector3.ProjectOnPlane(left, hit.normal);
        }
        else
        {
            Vector3 horiz = Vector3.ProjectOnPlane(left, Vector3.up);

            Vector3 wallNormal = Vector3.ProjectOnPlane(hit.normal, Vector3.up);

            if (wallNormal.sqrMagnitude < EPS)
                return position;

            wallNormal.Normalize();

            slide = Vector3.ProjectOnPlane(horiz, wallNormal);
        }

        float slideDist = slide.magnitude;
        if (slideDist < EPS)
            return position;

        Vector3 slideDir = slide / slideDist;

        GetCapsulePoints(position, out Vector3 sp1, out Vector3 sp2, out float sradius);

        if (Physics.CapsuleCast(sp1, sp2, sradius, slideDir,
                                out RaycastHit shit,
                                slideDist + SKIN, _worldMask, QTI))
        {
            float md2 = Mathf.Max(0f, shit.distance - SKIN);
            return position + slideDir * md2;
        }

        return position + slide;
    }
5. Привязка тоже проста: получаю очки капсулы отдельным методом (не буду вдаваться в подробности, это ненужно. Уверен, работает корректно) и закидываю капсулу вниз, проверяя, возможна ли привязка.

Код: Выделить всё

private Vector3 SnapToGround(Vector3 position)
    {
        GetCapsulePoints(position, out Vector3 p1, out Vector3 p2, out float radius);

        if (Physics.CapsuleCast(p1, p2, radius, Vector3.down,
                                out RaycastHit hit,
                                SNAP_DOWN_DIST + SKIN,
                                _worldMask, QTI)
            && CheckSlopeAngle(hit.normal))
        {
            float down = hit.distance - SKIN;
            position += Vector3.down * down;

            _isGrounded = true;
        }

        return position;
    }
В целом работает хорошо, но в конкретных случаях возникают проблемы. В играх, пока мы полностью не оторвемся от поверхности, мы вообще не упадем. Но мой контроллер начинает «болтаться», и мне это не нравится.
Изображение

Я знаю, что проблема в приведениях. Если бы я сделал обычный Raycast, нормаль к поверхности была бы возвращена правильно, и тогда столкновение с поверхностью было бы как в обычных играх. Но у меня были некоторые проблемы, о которых я уже, честно говоря, не помню (извините, я уже неделю работаю над этим контроллером).
Была идея заменить CapsuleCast и SphereCast на лучи, идущие по кругу, с радиусом капсулы игрока, вниз, и таким образом искать нормаль для проекции. Но я не уверен, что это хорошая идея.

Подробнее здесь: https://stackoverflow.com/questions/798 ... -rigidbody
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»