I am currently making a game in Unity, where characters are moving towards a point at the bottom of the screen, from a random x-position at the top of the screen. As they have to keep a constant speed (and later on possibly be able to move in specific patterns), I cannot use Vector3.Lerpfor this.
Instead, I tried using some simple math. StaticInfo.instance.GetPlayerPosition() is the targeted position. The code happens in the character's FixedUpdate().
float aVal = (myTransform.localPosition.y - StaticInfo.instance.GetPlayerPosition().y) /
(myTransform.localPosition.x - StaticInfo.instance.GetPlayerPosition().x);
float degrees = Mathf.Atan(aVal) * Mathf.Rad2Deg;
Vector3 newPos = new Vector3(myTransform.localPosition.x - (distance * speed * Mathf.Cos(degrees)),
myTransform.localPosition.y - (distance * speed * Mathf.Sin(degrees)),
1);
myTransform.localPosition = newPos;
My problem is when these characters are created (instantiated from prefab), they make a small 180-degrees loop before moving in the wanted direction. After that, they move exactly as wanted.
Is using math the best solution? And if it is, why does it do a funky drift when initialized?
This may be a more simple solution to what you are trying to achieve:
Vector3 moveDirection = (StaticInfo.instance.GetPlayerPosition() - myTransform.localPosition).normalized;
Vector3 newPos = myTransform.localPosition + moveDirection * distance * speed;
myTransform.localPosition = newPos;
If you want the object to point in the direction of the movement you can do:
myTransform.rotation = Quaternion.Euler(0, 0, Mathf.atan2(moveDirection.y, moveDirection.x)*Mathf.Rad2Deg - 90); // may not need to offset by 90 degrees here;
Related
I am new in Unity3D scripting especially when it comes to specifics of Transform, Quaternions, Vector3.
Down there is a script. When I roll the cube it flips in the right direction. However when cubes' local axis doesn't match the world axis the cube's endRotation is performed on the wrong axis.
Can anybody help me to resolve this issue : "endRotation" would flip the cube on right axis regardless of the relation between LOCAL axis <=> WorldAxis.
I was trying to resolve it for a week. Of cause no success.
Further down there code and video.
if (Input.GetAxis("Horizontal") > buttonDown)
{
StartCoroutine(FlipTheCube(Vector3.right));
return
}...
.
.
.
public IEnumerator FlipTheCube(Vector3 direction)
{
startFliping = false;
float rollStartTime = 0;
float rollAngle = 90;
float halfWidth = transform.localScale.z / 2;
Vector3 pointAround = transform.position + (Vector3.down * halfWidth) + (direction * halfWidth);
Vector3 rollAxis = Vector3.Cross(Vector3.up, direction);
Quaternion rotation = transform.rotation;
Quaternion **endRotation** = rotation * Quaternion.Euler(rollAxis * rollAngle);
Vector3 endPlacement = transform.position + direction;
float oldAngle = 0;
while (rollStartTime < rollDurtnTime)
{
yield return new WaitForEndOfFrame();
rollStartTime += Time.deltaTime;
float newAngle = (rollStartTime / rollDurtnTime) * rollAngle;
float rotateThrough = newAngle - oldAngle;
oldAngle = newAngle;
transform.RotateAround(pointAround, rollAxis, rotateThrough);
}
transform.position = endPlacement;
transform.rotation = **endRotation**;
startFliping = true;
}
Here is a link to the youtube video
First part of video If cubes local axis is matching world axis the "endRotation" is flipping correctly Second part of video as soon cubes local axis is not matching world axis "endRotation" goes wrong.
Your problems lie within these lines:
Vector3 rollAxis = Vector3.Cross(Vector3.up, direction);
Quaternion rotation = transform.rotation;
Quaternion endRotation = rotation * Quaternion.Euler(rollAxis * rollAngle);
// ...
transform.RotateAround(pointAround, rollAxis, rotateThrough);
First problem is Euler vs angle/axis usage.
If you've only been rotating against Vector3.right, Vector3.forward, and their negatives you might not have even noticed this yet.
In the first part, you use rollAxis as an Euler angle representation, and in the second part as an axis.
The problem is that for a rotation, its axis is usually not the same as a Euler representation of that rotation! For example, the rotation made by rotating around (0.7, 0.0, 0.7) by 90° is completely different from an Euler rotation of (63°, 0°, 63°)!
Instead, just use rollAxis consistently as an angle/axis representation. You can get the quaternion form using Quaternion.AngleAxis.
Second problem is global vs local rotation.
This is a problem whose effects you're definitely already noticing.
In the first part, you apply rollAxis as a local rotation. This is because rollAxis is the second term of the * operator.
In the second part, RotateAround rotates around a global axis defined by rollAxis.
Keep it global or local for both. Global is simpler, and it appears that's what you're trying to do (judging from transform.position + direction), so you should have rotation as the second term of the * operator.
Altogether, the problems can be fixed by just changing the Quaternion endRotation = rotation * Quaternion.Euler(rollAxis * rollAngle); line:
Vector3 rollAxis = Vector3.Cross(Vector3.up, direction);
Quaternion rotation = transform.rotation;
Quaternion endRotation = Quaternion.AngleAxis(rollAngle, rollAxis) * rotation;
// ...
transform.RotateAround(pointAround, rollAxis, rotateThrough);
See here for more information about the quaternion * operator.
I suggest you using DoTween asset (https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676):
DORotate(Vector3 to, float duration);
Using tweeners will save you a lot of time writing your own animations.
I'm making a game where you rotate your player and change gravity. the problem im having is to do with actually putting the rotation of the player, the keyboard inputs of the player and the current gravity together.
Line 1(Quaternion.FromToRotation) works completely fine until gravity is down(gravityDir(0, -1, 0)) or up(gravityDir(0, 1, 0)) and in that case the y component of movementInput will be inverted.
I can't just check if its one of the two and flip it back because I need it to be able to gradually change from one value to the other or otherwise be in between a different angle(like (1, 1, 0) for instance)
movementInput is just wasd put into a vector(w-s being x, a-d being z).
gravityDir is a vector for gravity(0, -1, 0) for down (0, 1, 0) being up (1, 0, 0) being positive x direction etc.
headAttach is a GameObject that controls the players head, I'm getting its y value in the code to rotate the movementInput vector so I can "move in the direction i'm facing".
movementInput = Quaternion.FromToRotation(gravityDir, Vector3.up) * movementInput;
movementInput = Quaternion.AngleAxis(headAttach.transform.localRotation.eulerAngles.y, transform.up) * movementInput;
rb.AddForce(movementInput * 5 * movementSpeed);
Don't use FromToRotationin this case, because you care about more than the direction of up. You also care about the directions of forward/right.
Instead, use LookRotation. You need to find the closest direction to the direction that the player is looking at (I assume that's headAttach.transform.forward) which is perpindicular to the direction of gravity. You can use cross products for that. Then, use that direction for the forward parameter of LookRotation and the direction opposite gravity for up:
Vector3 movementInput = Vector3.forward * Input.GetAxis("Vertical")
+ Vector3.right * Input.GetAxis("Horizontal");
Vector3 upDir = -gravityDir;
Vector3 lookDir = headAttach.transform.forward;
Vector3 rightDir = Vector3.Cross(upDir, lookDir);
Vector3 forwardDir = Vector3.Cross(rightDir, upDir);
movementInput = Quaternion.LookRotation(forwardDir, upDir) * movementInput;
rb.AddForce(movementInput * 5 * movementSpeed);
I am programming an 6DOF space-shooter in 3D with Unity and C#. I am using mouse and keyboard for the movement. With the mouse I use Quarterions for the rotation in X, Y and Z axis.
I tried the following code:
void Update()
{
float test = Input.GetAxis("Pitch");
float test2 = Input.GetAxis("Yaw");
float test3 = Input.GetAxis("Roll");
rotX += test;
Quaternion q1 = Quaternion.AngleAxis(rotX, Vector3.right);
rotY += test2;
Quaternion q2 = Quaternion.AngleAxis(rotY, Vector3.up);
roll += test3;
Quaternion q3 = Quaternion.AngleAxis(roll, Vector3.forward);
transform.rotation = q1 * q2 * q3;
}
The code works, but not for all the angles. It looks that over every 90 degrees the movement is not correct (x-axis and y-axis turn around). I expect that this code works for every angle, what is wrong in this code?
You are using absolute (world) vectors as your frame of reference. change Vector3.up to transform.up, Vector3.forward to transform.forward and Vector3.right to transform right, this way you will be rotating along local axis, and gimbal lock should be less problematic.
Order of operations will still matter, but for relatively small delta it should work as expected.
I think you are doing the right thing multiplying the quaternions before assigning the new rotation, otherwise each local vector would change with each operation - the behaviour would be slightly different
As #Zambari said, make sure you're rotating around local axes. But also, you seem to be storing the orientation in Euler angles, rotX, rotY, rotZ and then setting that as the absolute rotation. This is bad. What you want is to apply the delta/modification to the transform's stored quaternion:
void Update() {
var yaw = Quaternion.AngleAxis(Input.GetAxis("Yaw"), transform.up);
var pitch = Quaternion.AngleAxis(Input.GetAxis("Pitch"), transform.right);
var roll = Quaternion.AngleAxis(Input.GetAxis("Roll"), transform.forward);
transform.rotation = transform.rotation * yaw * pitch * roll;
}
note: the order of multiplication on the final line may be incorrect.
After saying all that, if all you want is to control your character, try (angles in degrees):
transform.Rotate(xAngle, yAngle, zAngle, Space.Self);
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)
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));
I want my object to keep moving in the target's direction forever or until it collides, the collision part I have already handled it; However, I am having problems with the movement part.
I first try to rotate my target using these lines of code
Vector2 diff = target - transform.position;
float angle = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0.0f, 0.0f, angle);
This works perfect and my object rotates in the direction I want it to.
In my Update method I have the following
if (isMoving)
{
Vector2 f = transform.forward;
transform.position = Vector3.MoveTowards(transform.position, target + Vector3.forward, speed * Time.deltaTime);
}
Now this runs but fails to accomplish the goal and I know why, it makes sense but not sure how to fix it. The object moves to the point in the correct direction but I don't want it to stop at the target, I want it to keep going.
I also tried
rb.MovePosition(rb.position + f * Time.deltaTime * speed);
rb is a rigidbody2D
as well as
rb.AddForce(rb.position + f * Time.deltaTime * speed);
But in both cases the object rotates but never moves
I also used translate and same behavior as MovePosition
P.S. It's a 2D game
After looking over the internet, I didn't find anything that really does what I am looking for, you are not able to translate a kinematic object forever (Obviously you have to handle that the object should get destroyed at some point or something, but that's not the issue). So I went ahead and decided to write my own library to make this simple. I was able to achieve my goal using the line equation. Simple I took these steps to solve my issue.
At start I get the slope between 2 points
Calculate the y-intercept
translate the object using the line equation with a fixed X value (step), every x value find the corresponding y value and translate the object to it.
Repeat step 3 in a FixedUpdate and that's it.
Of course there is more to it, handling cases where x = 0 which will give a 0 division for the slope or y = 0, etc... I solved all of that in the library. For anyone interested you can check it out here EasyMovement
If you don't want the library then here is a simple code that will do it.
//Start by Defining 5 variables
private float slope;
private float yintercept;
private float nextY;
private float nextX;
private float step;
private Vector3 direction; //This is your direction position, can be anything (e.g. a mouse click)
In the Start Method
//step can be anything you want, how many steps you want your object to take, I prefer 0.5f
step = 0.5f;
//Calculate Slope => (y1-y2)/(x1-x2)
slope = (gameObject.transform.position.y - direction.y) / (gameObject.transform.position.x - direction.x);
//Calculate Y Intercept => y1-x1*m
yintercept = gameObject.transform.position.y - (gameObject.transform.position.x * slope);
Now in FixedUpdate
//Start by calculating the nextX value
//nextX
if (direction.x > 0)
nextX = gameObject.transform.position.x + step;
else
nextX = gameObject.transform.position.x - step;
//Now calcuate nextY by plugging everything into a line equation
nextY = (slope * nextX) + yintercept;
//Finally move your object into the new coordinates
gameObject.transform.position = Vector3.MoveTowards(gameObject.transform.position, new Vector3(nextX, nextY, 0), speed * Time.deltaTime);
Keep in mind this won't do the heavy lifting, there are a lot of conditions that needs to be taken into consideration.