Управление, как в игре "Overcooked"

Вообще с вопросами "Как игра X сделала Y" лучше обращаться к самим разработчикам. Просто потому что зачастую довольно сложно воссоздать что-то из другой игры 1 в 1.

Данная статья - копия ответа на вопро с SO, которую я сохранил чисто для себя. Автор решения: @RiotBr3aker


Наверняка такое можно сделать и другим способом, но я решил пойти через 2 этапа:

  1. вращение персонажа в зависимости от ввода
  2. перемещение персонажа через transform.forward, благо персонаж в данной игре всегда двигается только лицом вперед

Ввод данных

Тут все максимально просто, я все это дело в вектор, чтобы потом получать через него углы и прочие нужные вещи:

private Vector3 moveVector;
...
void HandleInput() {
    moveVector.x = Input.GetAxis("Horizontal");
    moveVector.z = Input.GetAxis("Vertical");
}

Вращение

Казалось бы, можно просто использовать

transform.Rotate(Vector3.up, Vector3.Angle(transform.forward, moveVector));

Но Vector3.Angle возвращает наименьший угол между векторами, т.е. от 0 до 180, что приводит к таким результатам при вращении на угол от 0 до 360:

Один из выходов из данной ситуации - написать свою функцию определения угла между векторами для диапазона углов [0..360]:

float Angle360(Vector3 from, Vector3 to, Vector3 right) {
    float angle = Vector3.Angle(from, to);
    return (Vector3.Angle(right, to) > 90f) ? 360f - angle : angle;
}

А затем применяем все это дело в Update:

void Update () {
    HandleInput();
    transform.Rotate(Vector3.up, Angle360(transform.forward, moveVector, transform.right));
}

И получаем почти нужное нам вращение:


Перемещение

Все практически элементарно: используем Transform.Translate, не забывая добавить переменную для скорости:

public float speed = 2f;
...
transform.Translate(transform.forward * speed * Time.deltaTime, Space.World);

Данный код будет постоянно двигать объект по направлению его локального "прямо". Но мы хотим двигать его только при вводе пользователя.

Логичный выход из ситуации - использовать модуль вектора moveVector, предварительно его нормализовав, дабы получить значение в диапазоне [0..1]:

transform.Translate(
    moveVector.normalized.sqrMagnitude * transform.forward * speed * Time.deltaTime,
    Space.World
);

В данном случае можно использовать квадрат модуля просто потому что квадрат модуля всегда будет равен либо 0, либо 1, а sqrt(1) == 1/-1, sqrt(0) == 0. -1 нас по понятным причинам не беспокоит.

Но проще обойтись вообще без этого модуля и сделать вот так:

float Moves() {
    if(moveVector.x != 0 || moveVector.z != 0) {
        return 1f;
    }
    else {
        return 0f;
    }
}

Стоит отметить, что данный метод будет всегда выдавать вектор движения 1. Если игра поддерживает геймпады, где джойстики могут выдавать значения по обеим осям [0..1], лучше использовать модуль вектора.

И подставив результат этого метода в Translate.


В конечном итоге получили какой-то такой компонент:

public class OvercookedLikeMovement : MonoBehaviour {

    public float speed = 2f;

    private Vector3 moveVector;

    void Update () {
        HandleInput();

        transform.Rotate(Vector3.up, Angle360(transform.forward, moveVector, transform.right));

        transform.Translate(
            Moves() * transform.forward * speed * Time.deltaTime,
            Space.World
        );
    }

    void HandleInput() {
        moveVector.x = Input.GetAxis("Horizontal");
        moveVector.z = Input.GetAxis("Vertical");
    }

    float Moves() {
        if(moveVector.x != 0 || moveVector.z != 0)
            return 1f;
        else
            return 0f;
    }

    float Angle360(Vector3 from, Vector3 to, Vector3 right) {
        float angle = Vector3.Angle(from, to);
        return (Vector3.Angle(right, to) > 90f) ? 360f - angle : angle;
    }
}

Результат в Unity:


P.S.

Видно, что объект иногда двигается рывками. Причиной этому служит резкая смена угла поворота объекта, это не баг, а закономерный результат кода выше и чище без интерполяции не сделать.

Что бы я сделал с этим?

Посмотрел бы в сторону Tween движков для Unity, с помощью которых можно все это дело сгладить и сделать более натуральным, что ли. Но результат на данный момент, как по мне, очень похож на OverCooked.