Перемещение объекта относительно точки, на которую он всегда обращен, приводит к закручиванию орбиты по спирали

Я разрабатываю симуляцию, в которой игрок должен иметь возможность перемещаться внутри 2D-круга (называемого сферой в моем коде). Движение игроков должно быть относительно центра круга.

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

Когда я перемещаю игрока близко к центру круга и двигаюсь вбок (что относительно вектора, обращенного к игроку), игрок вращается вокруг центра, но затем начинает медленно двигаться наружу. Внешняя спираль намного более заметна вблизи центра и занимает около 8 витков, чтобы достичь внутреннего края круга. Вместо этого игрок должен вращаться вокруг центра на постоянном расстоянии от центра. Почему игрок разворачивается наружу?

Вот код, который я использую:

// center of the sphere
Vector3 center = sphereComponent.transform.position - player.transform.position;

// always rotate towards the center so that transform.up is
float angle = Vector3.Angle(center, Vector3.up);
float sign = (center.x < rigidbody.transform.position.x) ? 1.0f : -1.0f;
rigidbody.MoveRotation(angle * sign);

// use the input vector to calculate a vector relative to the objects right and up vectors
Vector2 relativeInputVector =
        (rigidbody.transform.right * player.InputVector.x) +
        (rigidbody.transform.up * player.InputVector.y);

// below is same as doing: rigidbody += relativeInputVector.normalized * 20 * Time.deltaTime;
rigidbody.MovePosition(rigidbody.position + (relativeInputVector.normalized * 20 * Time.deltaTime));

Итак, я уже пробовал несколько вещей:

  • Я думал, что это может быть проблема округления. Поэтому я округлил X и Y относительного входного вектора до второго десятичного знака. Не помогло.
  • Я нормализовал вектор relativeInputVector. Вроде не много сделал...
  • Я также подумал, может быть, мне следует двигаться, а затем вращаться, а не вращаться, а затем двигаться. Не работает.

Теперь я думаю, что проблема где-то в математике (вероятно, там, где я определяю relativeInputVector), но я не могу найти аналогичные варианты использования в этом отношении, чтобы я мог сравнивать и устранять неполадки.

(это довольно насыщенная тема, когда речь идет о ключевых словах, по которым я ищу)

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

Reactgular 01.06.2019 04:10

@Reactgular Не уверен, правильно ли я понимаю ваш комментарий. Однако я обновлю ответ, потому что забыл упомянуть, что спираль довольно тонкая. Так что это почти очень похоже на проблему с десятичной/плавающей запятой, но я не уверен.

FanManPro 01.06.2019 04:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
240
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

Вот диаграмма, показывающая, почему ваш код закручивается:

What your code does

То, как вы это делаете, угол между радиусом круга и положением игрока в начале кадра (A на диаграмме) и направлением движения твердого тела (1-> 2 на диаграмме) является прямым углом. В положении 1 радиус A может быть правильным расстоянием, но гипотенуза прямоугольного треугольника всегда длиннее каждого катета, поэтому новый радиус в положении 2 (B) должен быть больше, и, аналогично, C должен быть больше, чем B. .

Результатом этого является спиральное движение, поскольку вы продолжаете накапливать длину к радиусу, переключаясь с катетов на гипотенузы этих прямоугольных треугольников.

По сути, для того, чтобы ваш код работал, вам нужно будет создавать бесконечно маленькие треугольники — Time.deltaTime должны быть бесконечно малы — поскольку прямоугольный треугольник с одним бесконечно маленьким катетом — это просто линия, другой его катет и гипотенуза. имеют одинаковую длину.

Конечно, если бы Time.deltaTime было бесконечно мало, игрок никогда бы не двигался. ;) Итак, нужен другой подход:

an angular velocity approach

Вместо этого мы можем рассчитать угловую скорость игрока и затем перемещать игрока в соответствии с ней.

Итак, сначала определите новое расстояние игрока от центра, а затем сколько градусов игрок пройдет по кругу на этом радиусе:

Vector3 sphereCenterPoint = sphereComponent.transform.position

Vector3 playerToCenter = sphereCenterPoint  - player.transform.position;

float playerVerticalSpeed = 20f * player.InputVector.normalized.y;

newVerticalPosition = rigidbody.position + playerToCenter.normalized 
                                         * playerVerticalSpeed * Time.deltaTime;

playerToCenter = sphereComponent.transform.position - newVerticalPosition;

float circumferenceOfPlayerPath = 2f * playerToCenter.magnitude * Mathf.PI;

float playerHorizontalSpeed = 20f * player.InputVector.normalized.x;

float degreesTraveled = ( playerHorizontalSpeed * Time.deltaTime / circumferenceOfPlayerPath ) * 360f;

Затем поверните новое положение игрока по вертикали вокруг центральной точки и установите поворот и положение игрока соответственно. Вы можете использовать Quaternion.LookRotation, чтобы определить вращение, необходимое для того, чтобы твердое тело указывало вперед/вверх в желаемых направлениях:

// rotates newVerticalPosition around sphereCenterPoint by degreesTraveled around z axis
Vector3 newPosition = Quaternion.Euler(0f,0f, degreesTraveled) 
                     * (newVerticalPosition - sphereCenterPoint ) + sphereCenterPoint;

rigidbody.MovePosition(newPosition); 

rigidbody.MoveRotation(
        Quaternion.LookRotation(Vector3.forward, sphereCenterPoint - newPosition));

Чтобы убрать некоторые расчеты, вы можете включить часть, где вы делите на 2 пи и умножаете на 360f, в коэффициент 20f:

Vector3 sphereCenterPoint = sphereComponent.transform.position

Vector3 playerToCenter = sphereCenterPoint  - player.transform.position;

float playerVerticalSpeed = 20f * player.InputVector.normalized.y;

newVerticalPosition = rigidbody.position + playerToCenter.normalized 
                                         * playerVerticalSpeed * Time.deltaTime;

playerToCenter = sphereComponent.transform.position - newVerticalPosition;

float playerHorizontalSpeed = 1146f * player.InputVector.normalized.x;

float degreesTraveled = playerHorizontalSpeed * Time.deltaTime / playerToCenter.magnitude;

// rotates newVerticalPosition around sphereCenterPoint by degreesTraveled around z axis
Vector3 newPosition = Quaternion.Euler(0f,0f, degreesTraveled) 
                     * (newVerticalPosition - sphereCenterPoint ) + sphereCenterPoint;

rigidbody.MovePosition(newPosition); 

rigidbody.MoveRotation(
        Quaternion.LookRotation(Vector3.forward, sphereCenterPoint - newPosition));

Потребовалась небольшая доработка. SetLookRotation должен был быть просто LookRotation и должен был разобрать несколько Vector2 в/из Vector3. Также переменная playerHorizontalSpeed нигде не используется, но предполагается, что она должна использоваться для переменной degreesTraveled. После всего этого игрок смог сделать именно то движение, которое я хотел.

FanManPro 01.06.2019 05:29

Несмотря на то, что это дает мне желаемые результаты, эта часть все еще кажется очень посторонней: Instead, what you can do is determine the player's new distance from the center first, then how many degrees you would travel around the circle at that radius. Я надеюсь, что может быть более минимальный/эффективный подход. Я также не совсем понимаю участие Time.deltaTime и то, как это вызывает проблему. Вы предполагаете, что мой FPS колеблется во время игры в незначительном масштабе? Это все еще происходит, даже если я вынимаю Time.deltaTime...

FanManPro 01.06.2019 05:40

Удаление @FanusduToit Time.deltaTime эквивалентно Time.deltaTime из 1f. Не совсем "бесконечно маленький" ;). Я включил несколько диаграмм, чтобы объяснить, почему возникает спираль. Я также включил немного более краткую версию кода.

Ruzihm 01.06.2019 06:16

Большое "Ага!" момент, когда вы объяснили это с диаграммами. Теперь все имеет смысл. Бесконечно благодарен!

FanManPro 01.06.2019 06:23

Другие вопросы по теме