Issue When Moving Object to Specific Position Unity 3D - c#

I have an object that I want to move slowly & smoothly to a specific position when clicking, so I used this code:
currpos = transform.position;
Vector3 NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
Rigidbody.MovePosition(Vector3.Lerp(transform.position, NewPos, Time.deltaTime* MoveSpeed)) ;
The problem I have is when the MoveSpeed is low the object move a little bit and didn't reach the specific position, and when increasing the MoveSpeed he reach the specific position but quickly! do you have any suggestion?

public static Vector3 Lerp(Vector3 a, Vector3 b, float t);
Interpolates between the vectors a and b by the interpolant t. The
parameter t is clamped to the range [0, 1]. This is most commonly used
to find a point some fraction of the way along a line between two
endpoints.
As the Doc says, it is some fraction instead of distance. The logic isn't right.
So the code could be revised as follows:
currpos = transform.position;
Vector3 NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
Vector3 Dir= (NewPos-transform.position).normalized;
Vector3 velocity= Dir*MoveSpeed;
Rigidbody.MovePosition(transform.position+velocity);
If you insist using Lerp:
currpos = transform.position;
Vector3 NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
float dist= (NewPos-transform.position).magnitude;
if(dist!=0)
{
Rigidbody.MovePosition(Vector3.Lerp(transform.position, NewPos, Time.deltaTime* MoveSpeed)) ;
}
else
{
//do nothing new postion arrived
}

According to Unity3D documentation on Rigidbody.MovePosition you need to enable Rigidbody interpolation for smooth transition between the two positions. This should also be done in the FixedUpdate like this:
void FixedUpdate()
{
rb.MovePosition(transform.position + transform.forward * Time.deltaTime);
}
The Rigidbody.MovePosition takes just one parameter which is the destination Vector3. You should just specify destination point without the Lerping so try this instead:
Rigidbody.MovePosition(NewPos + Time.deltaTime * MoveSpeed);
And do it in the FixedUpdate. Also:
If the rigidbody has isKinematic set false then it works differently. It works like transform.position=newPosition and teleports the object (rather than a smooth transition).

Lerp Function gives a point between 2 Vectors everytime you call it, so just like the turtle problem, it will never reach to destionation. It will get slower and slower each time.
You can eliminate this behaviour with fixing travel speed to a constant but it will not look as good as slowing gradually as approach.
Also you can eliminate this behaviour with stopping the object after it gets too close to object, (that is how i use it usually)
Also note that, if you want to animate someting with code and you want it to be smooth you have to calculate it on "Update" Function.
So the code can be something like
void GoToTarget(){
var currpos = transform.position;
NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
traveling = true;
}
void Update () {
if (traveling){
rigidbody.MovePosition(Vector3.Lerp(transform.position, NewPos, Time.deltaTime* MoveSpeed));
if((transform.position-NewPos).magnitude < 0.1f){
//Current position is too close to Target, teleport to target and stop
transform.position = NewPos;
traveling = false;
}
}
}

Rather than lerping, why not just move towards it?
currpos = transform.position;
Vector3 NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
var position = Vector3.MoveTowards(transform.position, NewPos, Time.deltaTime* MoveSpeed);
Rigidbody.MovePosition(position);

Related

How to use LookRotation to rotate an object based on hand positions?

I'm creating an interaction system for a VR game and I'm struggling with two hand interactions. I'm using the Quaternion.LookRotation() method to generate the rotation of the grabbed object based on the positions and rotations of the hands. The forward part is pretty simple:
Vector3 fwd = (primaryHand.position - secondaryHand.position).normalized;
The "up" part is what I have difficulty with. Initially I tried using the average up direction of both hands:
Vector3 avgUp = Quaternion.Slerp(primaryHand.rotation, secondaryHand.rotation, 0.5f) * Vector3.up;
There is an issue with this approach: the hand's up vector might get aligned with the fwd vector, which causes the object to flip over when it goes over it. Here is a simple illustration of the problem:
The light green arrows represent the up direction of the hands, while the dark green is the calculated direction used as an argument for the LookRotation() method.
The obvious solution seems to be to pick a different fixed vector instead of "up", one which won't be so easily aligned with the fwd vector. In the example it could be a vector aligned with the fingers. But keep in mind that there are no restrictions on initial hand rotation so no matter which vector you choose the hands can always happen to be rotated so that the vectors align. And even if you pick the an optimal vector dynamically (one that is perpendicular to fwd), it's still at best 90 degrees from aligning with fwd.
To solve this I tried restricting the direction to the values which don't cause problems but this caused another issues (I had difficulties with determining which values are ok and which should be discarded). I feel like I'm doing something wrong here, is there any better solution to this problem?
You can calculate the delta rotations of the hands and apply it to the "base up" of the object (the new up if we only take into account the change in position of hands...which will of course be orthogonal to the axis of the object). Then determine the change in angle that results in that up being rotated with each hand. Average those angles out, then apply those angles with the hand-hand axis using Quaternion.AngleAxis to the "base up" from earlier. Then you have your forward and up for Quaternion.LookRotation).
Below is an example of how you can use this, including VR hand noise simulation. To see the test, create a new scene in unity and attach this to the camera and it will build the scene on play start. There is a grip/release gui that will appear in Play view. You can adjust the hand rotation in Scene view
Vector3 leftHandPosCurrent;
Vector3 rightHandPosCurrent;
Vector3 worldAxisPrev;
Quaternion leftHandRotPrev;
Quaternion leftHandRotCurrent;
Quaternion rightHandRotPrev;
Quaternion rightHandRotCurrent;
bool isGripping;
bool firstFrameGripping;
Rigidbody grippedRB;
Transform leftHand;
Transform rightHand;
Quaternion targetRot;
/*
* On subsequent frames of gripping, calculate deltas in positions and
* rotations, average out the hand's effects, then apply them to the gripped
* object
*/
void HandleGrippedRot()
{
Vector3 worldAxisCurrent = rightHandPosCurrent - leftHandPosCurrent;
if (!firstFrameGripping)
{
Vector3 prevUp = targetRot * Vector3.up;
// we haven't moved the transform based on the hands yet, so
// find the new up would be ignoring hand rotations
Vector3 newUp = Quaternion.FromToRotation(worldAxisPrev,
worldAxisCurrent) * prevUp;
float leftHandAngle = GetDegRot(newUp, leftHandRotPrev,
leftHandRotCurrent, worldAxisCurrent);
float rightHandAngle = GetDegRot(newUp, rightHandRotPrev,
rightHandRotCurrent, worldAxisCurrent);
float avgAngle = (leftHandAngle + rightHandAngle) * 0.5f;
newUp = Quaternion.AngleAxis(avgAngle, worldAxisCurrent) * prevUp;
targetRot = Quaternion.LookRotation(worldAxisCurrent,
newUp);
}
else
{
firstFrameGripping = false;
}
leftHandRotPrev = leftHandRotCurrent;
rightHandRotPrev = rightHandRotCurrent;
worldAxisPrev = worldAxisCurrent;
}
/*
* Given the "up" of the object without taking hand rotations into account
* and the axis, determine how a hand's delta rotation affects that up
* around the axis and return the angle of that rotation
*/
float GetDegRot(Vector3 baseUp, Quaternion prevHandRot, Quaternion curHandRot,
Vector3 axis)
{
Vector3 adjUp = (curHandRot * Quaternion.Inverse(prevHandRot)) * baseUp;
adjUp = Vector3.ProjectOnPlane(adjUp, axis);
return Vector3.SignedAngle(baseUp, adjUp, axis);
}
void Update()
{
AddVRNoise(leftHand);
AddVRNoise(rightHand);
leftHandPosCurrent = leftHand.position;
rightHandPosCurrent = rightHand.position;
leftHandRotCurrent = leftHand.rotation;
rightHandRotCurrent = rightHand.rotation;
if (isGripping)
{
HandleGrippedRot();
}
}
void StartGrip()
{
if (isGripping) return;
isGripping = true;
firstFrameGripping = true;
// grippedTransform is set accordingly at some point
}
void EndGrip()
{
if (!isGripping) return;
isGripping = false;
}
/*
* example of using targetRot to move rb
*/
private void FixedUpdate()
{
if (!isGripping) return;
Quaternion delta = targetRot
* Quaternion.Inverse(grippedRB.transform.rotation);
delta.ToAngleAxis(out float angle, out Vector3 axis);
// convert to shortest angle form
if (angle > 180f)
{
axis = -axis; angle = 360f - angle;
}
grippedRB.angularVelocity = angle * 0.25f * axis;
}
/*
* just for testing purposes
*/
void Start()
{
leftHand = CreateHand(true);
leftHand.position = Vector3.left;
rightHand = CreateHand(false);
rightHand.position = Vector3.right;
CreateArrow();
}
/*
* just for testing purposes
*/
void AddVRNoise(Transform hand)
{
Quaternion noise = Random.rotation;
noise.ToAngleAxis(out float angle, out Vector3 axis);
angle = 100f * Time.deltaTime;
noise = Quaternion.AngleAxis(angle, axis);
Quaternion noisyRot = hand.rotation * noise;
hand.rotation = noisyRot;
}
/*
* just for testing purposes
*/
void OnGUI()
{
if (GUI.Button(new Rect(0, 0, 100, 50), "Grip"))
{
StartGrip();
}
if (GUI.Button(new Rect(100, 0, 100, 50), "Release"))
{
EndGrip();
}
}
/*
* just for testing purposes
*/
Transform CreateHand(bool isLeft)
{
string handName = isLeft ? "Left" : "Right";
GameObject hand = new GameObject($"{handName}hand");
GameObject palm = GameObject.CreatePrimitive(PrimitiveType.Cube);
palm.transform.localScale = new Vector3(0.5f, 0.2f, 1f);
palm.transform.SetParent(hand.transform);
GameObject thumb = GameObject.CreatePrimitive(PrimitiveType.Cube);
thumb.transform.localScale = new Vector3(0.2f, 0.1f, 0.1f);
thumb.transform.SetParent(hand.transform);
thumb.transform.localPosition = new Vector3(isLeft ? 0.32f : -0.32f,
0f, -.31f);
return hand.transform;
}
/*
* just for testing purposes
*/
void CreateArrow()
{
GameObject arrow = new GameObject();
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube);
body.transform.localScale = new Vector3(1f, 1f, 5f);
body.transform.SetParent(arrow.transform);
GameObject head = GameObject.CreatePrimitive(PrimitiveType.Cube);
head.transform.SetParent(arrow.transform);
head.transform.localEulerAngles = Vector3.up * 45f;
head.transform.localPosition = new Vector3(0f, 0f, 2.5f);
grippedRB = arrow.AddComponent<Rigidbody>();
grippedRB.useGravity = false;
arrow.transform.position = 2f * Vector3.up;
}

Rotating raycast2D back and forth with object

I made a circle and attach a lazer box on top of it.
The lazer will fire a raycast to its upper y axis (straight up). I also add a line renderer to view it.
I want the raycast to rotate 90 degrees back and forth. Sort of like its scanning everything on top. My problem is that its not working properly. It does rotate back and forth but If I move the x position of the lazer object, the raycast will rotate in a weird angle.
Script for lazer object
public LineRenderer lineRenderer;
public LayerMask layerMask;
public float laserSpeed;
Vector3 pointA;
Vector3 pointB;
Vector3 castPosition;
RaycastHit2D rayCast;
float time;
void Start()
{
pointA = transform.eulerAngles + new Vector3(0f, 0f, 90f);
pointB = transform.eulerAngles + new Vector3(0f, 0f, -90f);
}
void Update()
{
time = Mathf.PingPong(Time.time * laserSpeed, 1);
transform.eulerAngles = Vector3.Lerp(pointA, pointB, time);
castPosition = new Vector3(transform.position.x, transform.position.y, transform.position.z);
rayCast = Physics2D.Raycast(castPosition, transform.TransformDirection(Vector2.up), 10f, layerMask);
lineRenderer.SetPosition(0, castPosition);
lineRenderer.SetPosition(1, transform.TransformDirection(Vector2.up) * 10f);
}
Using eulerAngles for continuous animations is quite "dangerous". Unity stores the rotations as Quaternion and there are multiple ways of how to represent these in euler space!
When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation.
To avoid these kinds of problems, the recommended way to work with rotations is to avoid relying on consistent results when reading .eulerAngles particularly when attempting to gradually increment a rotation to produce animation. For better ways to achieve this, see the Quaternion * operator.
so you should rather go for Quaternion and do e.g.
And then you are using transform.TransformDirection(Vector2.up) which is a direction and pass it to your line renderer as a position.
What you want there is rather the position combined from
transform.position + transform.up
So together it should probably rather be
public LineRenderer lineRenderer;
public LayerMask layerMask;
public float laserSpeed;
private Quaternion originalRotation;
private Quaternion minRotation;
private Quaternion maxRotation;
void Start()
{
originalRotation = transform.rotation;
minRotation = originalRotation * Quaternion.Euler(0, 0, -90);
maxRotation = originalRotation * Quaternion.Euler(0, 0, 90);
}
void Update()
{
// Note that Vector3 is a "struct" -> there is no need to manually use "new Vector3(transform.position.x, ...)"
var startPosition = transform.position;
lineRenderer.SetPosition(0, startPosition);
var factor = Mathf.PingPong(Time.time * laserSpeed, 1);
// instead of the eulers rather use Quaternion
transform.rotation = Quaternion.Lerp(minRotation, maxRotation, factor);
// "transform.up" basically equals using "transform.TransformDirection(Vector3.up)"
var rayCast = Physics2D.Raycast(startPosition, transform.up, 10f, layerMask);
if(rayCast.collider)
{
// when you hit something actually use this hit position as the end point for the line
lineRenderer.SetPosition(1, rayCast.point);
}
else
{
// otherwise from the start position go 10 units in the up direction of your rotated object
lineRenderer.SetPosition(1, startPosition + transform.up * 10f);
}
}

Unity movement near point

I have spaceships that chase each other. They currently move to their target exactly, but I want to replicate physics you might see on water or in zero gravity, where the object would overshoot its target, turn and move back toward it. Possibly hovering around the target back and forth. I've tried addforce and addrelativeforce, and velocity, but those don't seem to give me the desired effect. Any ideas?
This is my code...
Vector3 dir = (new Vector3(x, y, 0) - transform.parent.position).normalized;
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
float heading = Mathf.Atan2(dir.x, dir.y);
transform.parent.rotation = Quaternion.Slerp(transform.parent.rotation, Quaternion.Inverse(Quaternion.Euler(0f, 0f, heading * Mathf.Rad2Deg)), Time.deltaTime * 12f);
//transform.parent.position += dir * speed * Time.deltaTime;
//rb.AddForce(dir);
rb.velocity = new Vector3(dir.x * speed, dir.y * speed, 0);
Here's how I achieve that AI in a 2D space shooter:
void FixedUpdate()
{
var target = Player != null ? PlayerObject : Target; // If player is dead, go for secondary target
Vector2 dir = -(Vector2)(transform.position - target.transform.position + targetOffset); // Direction
// dir.magnitude is the distance to target
if (dir.magnitude > reaction * rangeModifier) // If I'm far away from the target, slowly rotate towards it
{
// calculate angle toward target and slowly rotate child ship object toward it in the speed of turnRate
float attackAngle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90;
ship.transform.rotation = Quaternion.Slerp(ship.transform.rotation, Quaternion.AngleAxis(attackAngle, Vector3.forward), turnRate * Time.deltaTime);
}
else // If I'm close to the target just keep going straight ahead and fire until I'm too far away again
{
My.weapon.Shoot(dir);
}
_rb.velocity = ship.transform.up.normalized * My.Movementspeed; // Add velocity in the direction of the ship's rotation
}
This will give you an AI that goes in eight (8) shapes around the target. If you have many objects running this code I recommend adding a random offset to the target, to simulate swarming and more realistic flight aim.
I added comments to explain some details of the code.

Unity Camera Quaternion.RotateTowards without rolling/banking

I have a camera facing the ground and I want to pan up to look at a target object in the distance.
Currently, I achieve this with the following:
Vector3 dir = targetPoint - transform.position;
Quaternion lookRotation = Quaternion.LookRotation(dir);
Quaternion newRotation = Quaternion.RotateTowards(transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
transform.rotation = newRotation;
The camera performs the rotation and ends up pointing at the target object correctly, but as the camera pans up it tilts to one side making my game world set at an angle to the viewer, which is pretty disorienting:
How can I constrain the camera angle some way so that the horizon is always flat to the camera?
Thanks!
Update
Adding the line suggested by #Isaac below produces the correct rotation in relation to the horizon, but it snaps abruptly to z=0 at the start which is still not what I'm looking for.
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, 0);
There is an excellent Q/A on gamedev.stackexchange on this subject. You should try the pitch/yaw system suggested there.
Another suggestion is to correct for the roll of your camera during the rotation.
public float rollCorrectionSpeed;
public void Update()
{
float roll = Vector3.Dot(transform.right, Vector3.up);
transform.Rotate(0, 0, -roll * rollCorrectionSpeed);
Vector3 dir = targetPoint.position - transform.position;
Quaternion lookRotation = Quaternion.LookRotation(dir);
Quaternion newRotation = Quaternion.RotateTowards(transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
transform.rotation = newRotation;
}
Edit:
There is an easier solution: Just keep the z rotation of the Quaternion you are rotating from to 0.
public void Update()
{
Vector3 angles = transform.rotation.eulerAngles;
Quaternion from = Quaternion.Euler(angles.x, angles.y, 0);
Vector3 dir = targetPoint.position - transform.position;
Quaternion to = Quaternion.LookRotation(dir);
transform.rotation = Quaternion.RotateTowards(from, to, rotationDamping * Time.deltaTime);
}
Upon experimentation, I found 2 possible solutions depending on what you want.
If you are just trying to follow the target I would recommend using LookAt, which automatically aligns to the world up. In your code that would be (in Update) transform.LookAt(dir);.
If you need/want the pan effect set the localEulerAngles after updating the rotation. This is what I did which worked:
//this is your code
Vector3 dir = targetPoint - transform.position;
Quaternion lookRotation = Quaternion.LookRotation(dir);
Quaternion newRotation = Quaternion.RotateTowards(transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
transform.rotation = newRotation;
//this is what I added
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, 0);
The addition simply takes the way the camera is facing after updating the rotation using the quaternion and sets the z rotation to zero.
Let me know if you have any questions :)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
new information/edits:
I believe I have found a solution, but it is ugly and I would appreciate feedback as to whether it stutters, etc.
This code is essentially the same as before, but now it checks to see the z angle and edits it more manually using a variable I called zDamping which affects the speed at which the camera rotates around the z access only.
I added outside of update:
public float zDamping; //public only for testing, it's convenient for finding an optimal value
private bool rotationCheck = false;
And then inside update():
//This is your code (unchanged)
Vector3 targetPoint = target.transform.position;
Vector3 dir = targetPoint - transform.position;
Quaternion lookRotation = Quaternion.LookRotation (dir);
Quaternion newRotation = Quaternion.RotateTowards (transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
//This is what is new (remove my addition from before edits or it won't work)
if (transform.localEulerAngles.z >= 180f && transform.localEulerAngles.z <= 359f && !rotationCheck) {
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, transform.localEulerAngles.z + (rotationDamping * zDamping));
transform.rotation = newRotation;
}
else if (transform.localEulerAngles.z <= -180f && transform.localEulerAngles.z >= 1f && !rotationCheck) {
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, transform.localEulerAngles.z - (rotationDamping * zDamping));
transform.rotation = newRotation;
}
else {
transform.rotation = newRotation;
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, 0);
rotationCheck = true;
}
As I said, this solution is pretty ugly but it might work. You'll have to see what zDamping values work for your speeds to look natural (I recommend starting with .01). There will also be a small "jump" once you get close to the value, but the closer you make 359f to 360 and 1f to 0 the smaller that jump will be. The danger with making it too small is if you overshoot, but it should work even if it overshoots, but it will take a small amount of time.
Test it out and let me know what you think, sorry I couldn't find something more elegant right now. I also experimented with adding a separate Quaternion to exclusively rotate the z axis, but it did not work; feel free to experiment with that and if you want I can give more details about what I did.
Good luck and again, sorry for the sloppy solution.
Added a code in your line, hopefully it'll solve the problem.
Vector3 dir = targetPoint - transform.position;
Quaternion lookRotation = Quaternion.LookRotation(dir);
Quaternion newRotation = Quaternion.RotateTowards(transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
newRotation.eulerAngles = new Vector3(newRotation.eulerAngles.x,newRotation.eulerAngles.y,transform.rotation.eulerAngles.z);
transform.rotation = newRotation;

Move GameObject back and forth

I got an Object that I want to move up to Point A and when it reaches Point A it should move to Point B. When it reaches Point B it should move back to Point A.
I thought I could use Vector3.Lerp for this
void Update()
{
transform.position = Vector3.Lerp(pointA, pointB, speed * Time.deltaTime);
}
But how can i move back then? Is there an elegant way to archieve this? Obviously I would need 2 Lerps like this way:
void Update()
{
transform.position = Vector3.Lerp(pointA, pointB, speed * Time.deltaTime); // Move up
transform.position = Vector3.Lerp(pointB, pointA, speed * Time.deltaTime); // Move down
}
Could someone help me out?
There are many ways to do this but Mathf.PingPong is the easiest and the simplest way to accomplish this. Use Mathf.PingPong to get number between 0 and 1 then pass that value to Vector3.Lerp. That's it.
Mathf.PingPong will automatically return value will that will move back and forth between 0 and 1. Read the linked documentation for more info.
public float speed = 1.19f;
Vector3 pointA;
Vector3 pointB;
void Start()
{
pointA = new Vector3(0, 0, 0);
pointB = new Vector3(5, 0, 0);
}
void Update()
{
//PingPong between 0 and 1
float time = Mathf.PingPong(Time.time * speed, 1);
transform.position = Vector3.Lerp(pointA, pointB, time);
}

Categories