В целях обучения делаю прототип игры вместе с Claude. Такой подход мне нравится больше: сперва мы создаем план того как должна выглядеть фича, и затем поэтапно реализовываем. По ходу могу задавать любые вопросы и уходить вглубь по интересующим темам. Управление персонажем тоже реализовывается с нуля: ходьба и поворот камеры. Конечно, есть уже готовые инструменты от Unity, но в них очень много абстракций, которые могут сбивать и усложнять понимание, плюс это быстро становится no fun.
И вот при реализации движения был замечен интересный эффект.
Это часть кода, которая была в изначальной версии:
void HandleMovement()
{
float x = Input.GetAxisRaw("Horizontal");
float z = Input.GetAxisRaw("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
if (characterController.isGrounded && verticalVelocity < 0)
verticalVelocity = -1f;
else
verticalVelocity += gravity * Time.deltaTime;
Vector3 finalMove = move * speed + Vector3.up * verticalVelocity;
characterController.Move(finalMove * Time.deltaTime);
}
Главное здесь это первые три строки, именно там собирается вектор движения. С виду вроде бы ничего необычного: для того, чтобы персонаж двигался, мы получаем значения по осям ввода (x, z), перемножаем с векторами направления самого объекта GameObject (transform.right, transform.forward) и складываем, получая вектор движения.
Но в этой реализации есть “баг”. Если зажать одновременно клавиши движения вперед/назад и влево/вправо, игрок будет двигаться быстрее.
Чтобы понять, что происходит нужно вспомнить формулу того как считается длина вектора:
Когда мы жмём только вперед:
Vector3 move = transform.forward * 1 = (0, 0, 1) // длина вектора = 1
Когда жмём только вправо:
Vector3 move = transform.right * 1 = (1, 0, 0) // длина вектора = 1
А когда жмём вперед+вправо одновременно:
Vector3 move = transform.right * 1 + transform.forward * 1 = (1, 0, 1)
То длина вектора по формуле будет следующей:
Что почти в полтора раза быстрее.
Это не баг Unity, а чистая геометрия: диагональ квадрата длиннее его стороны.
Этот эффект известен как strafe-running. Именно на нём построена легендарная техника strafe-jumping из Quake и Doom, где движение по диагонали (плюс прыжки) позволяло ускорять персонажа эксплуатируя механику для спидранов или в PvP.
Фиксится это в Unity супер просто. Нам необходимо нормализовать вектор, т.е. нужно каждую компоненту поделить на длину вектора. В формуле это будет выглядеть так:
Тут мы видим, что нормализация сводит длину вектора по диагонали к 1, как раз то, что нам нужно.
Но писать нормализацию самим не нужно, ведь в Unity в Vector3 есть метод .normalized, который делает всю магию за нас. Поэтому конечная формула для движения персонажа будет следующей:
float x = Input.GetAxisRaw("Horizontal");
float z = Input.GetAxisRaw("Vertical");
Vector3 move = (transform.right * x + transform.forward * z).normalized;
После этого простейшего фикса наш персонаж будет двигаться во всех направлениях с одинаковой скоростью.