Я разрабатываю симуляцию, в которой игрок должен иметь возможность перемещаться внутри 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));
Итак, я уже пробовал несколько вещей:
Теперь я думаю, что проблема где-то в математике (вероятно, там, где я определяю relativeInputVector), но я не могу найти аналогичные варианты использования в этом отношении, чтобы я мог сравнивать и устранять неполадки.
(это довольно насыщенная тема, когда речь идет о ключевых словах, по которым я ищу)
@Reactgular Не уверен, правильно ли я понимаю ваш комментарий. Однако я обновлю ответ, потому что забыл упомянуть, что спираль довольно тонкая. Так что это почти очень похоже на проблему с десятичной/плавающей запятой, но я не уверен.





Ваша интуиция имела бы смысл, если бы вы двигались в сторону, а затем одновременно и непрерывно корректировали направление вектора движения вперед, но это делается попеременно и дискретно.
Подумайте, что произойдет, если Time.deltaTime будет абсолютно огромным для одного кадра. Вы бы сильно уклонились, может быть, даже ушли бы за пределы экрана в одном направлении, а затем изменили бы угол, чтобы оказаться лицом к центру круга. Это преувеличенный пример, но это именно то, что происходит в небольших масштабах.
Вот диаграмма, показывающая, почему ваш код закручивается:
То, как вы это делаете, угол между радиусом круга и положением игрока в начале кадра (A на диаграмме) и направлением движения твердого тела (1-> 2 на диаграмме) является прямым углом. В положении 1 радиус A может быть правильным расстоянием, но гипотенуза прямоугольного треугольника всегда длиннее каждого катета, поэтому новый радиус в положении 2 (B) должен быть больше, и, аналогично, C должен быть больше, чем B. .
Результатом этого является спиральное движение, поскольку вы продолжаете накапливать длину к радиусу, переключаясь с катетов на гипотенузы этих прямоугольных треугольников.
По сути, для того, чтобы ваш код работал, вам нужно будет создавать бесконечно маленькие треугольники — Time.deltaTime должны быть бесконечно малы — поскольку прямоугольный треугольник с одним бесконечно маленьким катетом — это просто линия, другой его катет и гипотенуза. имеют одинаковую длину.
Конечно, если бы Time.deltaTime было бесконечно мало, игрок никогда бы не двигался. ;) Итак, нужен другой подход:
Вместо этого мы можем рассчитать угловую скорость игрока и затем перемещать игрока в соответствии с ней.
Итак, сначала определите новое расстояние игрока от центра, а затем сколько градусов игрок пройдет по кругу на этом радиусе:
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. После всего этого игрок смог сделать именно то движение, которое я хотел.
Несмотря на то, что это дает мне желаемые результаты, эта часть все еще кажется очень посторонней: 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...
Удаление @FanusduToit Time.deltaTime эквивалентно Time.deltaTime из 1f. Не совсем "бесконечно маленький" ;). Я включил несколько диаграмм, чтобы объяснить, почему возникает спираль. Я также включил немного более краткую версию кода.
Большое "Ага!" момент, когда вы объяснили это с диаграммами. Теперь все имеет смысл. Бесконечно благодарен!
Я не читал это подробно, но вам нужен вектор вверх, чтобы смотреть на движущуюся точку, не переворачивая и не вращаясь. Точка также не может двигаться прямо под вектором вверх.