Управление, как в игре "Overcooked"
Вообще с вопросами “Как игра X сделала Y” лучше обращаться к самим разработчикам. Просто потому что зачастую довольно сложно воссоздать что-то из другой игры 1 в 1.
Данная статья - копия ответа на вопро с SO, которую я сохранил чисто для себя. Автор решения: @RiotBr3aker
Наверняка такое можно сделать и другим способом, но я решил пойти через 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.