问题
I'm developing a simulation where player should be able to move around inside a 2D circle (referred to as sphere in my code). The players movement must be relative to the center of the circle.
My first step was to make sure the player always faces the center. I got the working fine. However when I tried to do the relative movement it doesn't give my quite the result I'm looking for.
When I move the player close to the center of circle and move sideways (which is relative to the player's facing vector), the player spins around the center but then slowly starts spiraling outwards. The outwards spiral is much more prominent near the center and takes about 8 orbits to reach the inner edge of the circle. Instead the player should be spinning around the center at a constant distance from the center. Why does the player spiral outwards?
Here is the code I use:
// 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));
So I've tried a few things already:
- I thought it was maybe a rounding issue. So I rounded the relativeInputVector's X and Y to the 2nd decimal place. Didn't help.
- I normalized the relativeInputVector vector. Didn't seem to do much...
- I also thought maybe I should move and then rotate instead of rotate then move. Didn't work.
Now I'm thinking the issue is somewhere in the math (probably where I define relativeInputVector) but I can't find simular use cases regarding this so that I can compare and troubleshoot.
(this is a rather saturated topic when it comes to the keywords I'm search with)
回答1:
Your intuition would make sense if you were moving to the side then adjusting the direction of your forward vector simultaneously and continuously, but it's being done alternating and discretely.
Consider what happens if Time.deltaTime
was absolutely enormous for one frame. You would sidestep a huge amount, maybe even going off the screen in one direction, and then you would adjust your angle to face the center of the circle. That's an exaggerated example but its exactly what's happenening on a small scale.
Here's a diagram showing why your code spirals out:
The way you're doing it, The angle between the circle's radius to the player's position at the beginning of the frame (A in the diagram) and the direction the rigidbody moves (1->2 in the diagram) is a right angle. At position 1, the radius A might be the correct distance, but the hypotenuse of a right triangle is always longer than each leg, so the new radius at position 2 (B) must be larger, and likewise, C must be larger than B.
The result of that is a spiral motion as you continue to accumulate length to your radius by switching from legs to hypotenuses of these right triangles.
Basically, in order for your code to work, you would need to be making infinitely small triangles--Time.deltaTime
would need to be infinitely small--as a right triangle with one infinitely small leg is just a line, its other leg and its hypotenuse are the same length.
Of course if Time.deltaTime
were infinitely small, the player would never move. ;) So, a different approach is needed:
Instead, we can calculate the player's angular velocity and then move the player according to that.
So, dirst determine the player's new distance from the center first, then how many degrees the player would travel around the circle at that radius:
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;
Then, rotate the player's new vertical position around the center point and set the player's rotation and position accordingly. You can use Quaternion.LookRotation
to determine the rotation needed to make the rigidbody point forward/up in desired directions:
// 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));
To remove a few calculations, you can include the part where you divide by 2 pi and multiply by 360f into the 20f factor:
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));
来源:https://stackoverflow.com/questions/56403327/move-object-relative-to-a-point-it-is-always-facing-causes-spiraling-orbit