I am rotating my camera to lookat a target object using Quaternion. I am using Quaternion.LookRotation and Quaternion.Slerp to make the camera rotate smoothly inside coroutine. Unfortunately the camera jitters a lot when rotating. How do I make the rotation smooth with my current code?
Quaternion targetRotation;
public Transform lookAtObject;
IEnumerator RotateTowardsTarget () {
var duration = 2.0f;
for (float t = 0.0f; t < duration; t += Time.deltaTime) {
targetRotation = Quaternion.LookRotation (lookAtObject.position - transform.position);
transform.rotation = Quaternion.Slerp (transform.rotation, targetRotation, t / duration);
yield return null;
}
}
You currently always start the lerp at the current rotation towards the target.
In your usecase what you rather want to do is store the initial rotation and rather interpolate between the initial and target rotation like
IEnumerator RotateTowardsTarget ()
{
var duration = 2.0;
// store the initial and target rotation once
var startRotation = transform.rotation;
var targetRotation = Quaternion.LookRotation (lookAtObject.position - transform.position);
for (var timePassed = 0.0f; timePassed < duration; timePassed += Time.deltaTime)
{
var factor = timePassed / duration;
// optionally add ease-in and -out
//factor = Mathf.SmoothStep(0, 1, factor);
transform.rotation = Quaternion.Slerp (startRotation, targetRotation, factor);
yield return null;
}
// just to be sure to end up with clean values
transform.rotation = targetRotation;
}
This will make the camera go from the current to the new rotation linear within 2 seconds
Slerp takes third parameter as progress not as actual time you can do this
void Update () {
StartCoroutine(RotateOverTime(transform.rotation,
lookAtObject.rotation, 1f / speed));
}
IEnumerator RotateOverTime (Quaternion originalRotation, Quaternion
finalRotation, float duration) {
if (duration > 0f) {
float startTime = Time.time;
float endTime = startTime + duration;
transform.rotation = originalRotation;
yield return null;
while (Time.time < endTime) {
float progress = (Time.time - startTime) / duration;
// progress will equal 0 at startTime, 1 at endTime.
transform.rotation = Quaternion.Slerp (originalRotation,
finalRotation, progress);
yield return null;
}
}
transform.rotation = finalRotation;
}
You can now send in duration as seconds
Related
Im trying to make a peice of code that instantiates a gameobject, set its rotation to face the direction of the cursor from the player character at that moment and move towards that direction with constant speed for 2 seconds then stop. However my piece of coded is moving the gameobject towards the cursor direction but the speed gets changed depending on how far my cursor is from the player character.
private IEnumerator Rake()
{
Vector3 relativepos =
Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
Quaternion rotation = Quaternion.LookRotation(relativepos, Vector3.up);
float timepassed = 0;
GameObject WcastRB =
Instantiate(Wcast, gameObject.transform.position, rotation);
Rigidbody2D rg;
rg = WcastRB.GetComponent<Rigidbody2D>();
while (timepassed < 2)
{
timepassed += Time.deltaTime;
rg.velocity = WcastRB.transform.forward * 1000 * Time.deltaTime;
if (timepassed >= 2)
{
rg.velocity = WcastRB.transform.forward * 0;
}
yield return null;
}
}
this is what I have made.
Try this out, I found that because transform.forward was dependent on the object's rotation(and subsequently the original click position).
When the click was too close to the object the transform.forward Vector2 had a magnitude(length) of less than one, causing the speed to slow down.
By increasing the magnitude of the velocity to exactly 1f it should go consistent speeds in all directions
IEnumerator Rake()
{
Vector3 relativepos = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
Quaternion rotation = Quaternion.LookRotation(relativepos, Vector3.up);
float timepassed = 0;
GameObject WcastRB = Instantiate(Wcast, gameObject.transform.position, rotation);
Rigidbody2D rg;
rg = WcastRB.GetComponent<Rigidbody2D>();
Vector2 velocity = rg.transform.forward;
velocity.Normalize();
while (timepassed < 2)
{
timepassed += Time.deltaTime;
rg.velocity = velocity * 1000 * Time.deltaTime;
if (timepassed >= 2)
{
rg.velocity = new Vector2();
}
yield return null;
}
}
Edit:
Removed self-implemented .Normalize() because I forgot that i also used the built in .Normalize() literally right before it.
I have a problem with moving from one place to another in Unity over time. I would like my character to move from current position to current + 1 on Y. Unfortunately, looks like it does not get the current position properly or something, since if I debug what I wrote, it says that the magnitude is always 1, so the point is moving with me. Shouldn't it just check the current position and add 1 to Y, move to that position and then check again? I have no idea what's wrong with this code, or if it's strictly connected with how Unity checks positions and things in real time?
public bool moving = false;
private Vector3 dir;
void FrontMovement()
{
Vector3 movement = new Vector3(-Mathf.Sin(transform.eulerAngles.z * Mathf.PI / 180), Mathf.Cos(transform.eulerAngles.z * Mathf.PI / 180), 0f); // always moving front, even after rotation
if (moving == false)
{
dir = movement - transform.position;
moving = true;
return;
}
transform.Translate(dir.normalized * Time.deltaTime);
if(dir.magnitude <= Time.deltaTime)
{
Debug.Log("Finished movement");
moving = false;
}
}
void FixedUpdate()
{
Debug.Log(dir.magnitude);
FrontMovement();
}
I would also like to know how to do rotations over time.
https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
lerp also works for rotations
// Movement speed in units per second.
public float speed = 1.0F;
// Time when the movement started.
private float startTime;
// Total distance between the markers.
private float journeyLength;
void StartMoving() {
// Keep a note of the time the movement started.
startTime = Time.time;
Vector3 modifiedPosition = transform.position;
transform.position.y += 1.0f;
// Calculate the journey length.
journeyLength = Vector3.Distance(transform.position, modifiedPosition.position);
moving = true;
}
// Move to the target end position.
void Update()
{
if (moving) {
// Distance moved equals elapsed time times speed..
float distCovered = (Time.time - startTime) * speed;
// Fraction of journey completed equals current distance divided by total distance.
float fractionOfJourney = distCovered / journeyLength;
// Set our position as a fraction of the distance between the markers.
transform.position = Vector3.Lerp(startMarker.position, endMarker.position, fractionOfJourney);
if (fractionOfJourney >= 1.0f) {
moving = false;
}
}
}
You could use Coroutine + Vector3.Lerp to move with a specified amount of time:
public IEnumerator MoveToPosition(Transform transform, Vector3 position, float timeToMove)
{
var currentPos = transform.position;
var t = 0f;
while(t <= 1f)
{
t += Time.deltaTime / timeToMove;
transform.position = Vector3.Lerp(currentPos, position, t);
yield return null;
}
transform.position = position;
}
you call the coroutine in Start Method
StartCoroutine(MoveToPosition(transform, newposition, timeToMove))
You could use the same logic for Rotating, with Quaternion.Lerp or Slerp and Quaternion.LookRotation, of course you have lot of sample with rotation over time on WEB!! google is your friend...
public IEnumerator RotateToDirection(Transform transform, Vector3 position, float timeToRotate)
{
var startRotation = transform.rotation;
var direction = position - transform.position;
var finalRotation = Quaternion.LookRotation(direction);
var t = 0f;
while (t <= 1f)
{
t += Time.deltaTime / timeToRotate;
transform.rotation = Quaternion.Lerp(startRotation, finalRotation, t);
yield return null;
}
transform.rotation = finalRotation;
}
I have a large dataset that contains the position data for a vehicle at 0.1 second intervals and I have been trying to take this data and animate a gameobject smoothly along these positions.
I have tried Using AnimationCurves and adding keyframes for the individual axis similar to this
public AnimationCurve ac;
public AnimationCurve ac1;
public Vector3 pos1 = new Vector3(0.0f, 0.0f, 0.0f);
public Vector3 pos2 = new Vector3(0.0f, 0.0f, 0.0f);
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine(Move(pos1, pos2, ac, ac1, 3.0f));
}
}
IEnumerator Move(Vector3 pos1, Vector3 pos2, AnimationCurve ac, AnimationCurve ac1, float time)
{
float timer = 0.0f;
while (timer <= time)
{
var x = Mathf.Lerp(pos1.x, pos2.x, ac.Evaluate(timer / time));
var y = Mathf.Lerp(pos1.y, pos2.y, ac1.Evaluate(timer / time));
transform.position = new Vector3(x, y, 0);
timer += Time.deltaTime;
yield return null;
}
}
However I couldnt figure out how to turn the splitsecond positional data into the correct values for the AnimationClip.
I also tried using looping over the positions with coroutines similar to this:
var coords = vehicles[0].middleCoords;
var previousStep = coords[0];
foreach(Vector3 step in coords)
{
MoveOverSeconds(step, 0.1f);
}
public IEnumerator MoveOverSeconds(Vector3 end, float seconds)
{
yield return new WaitForSeconds(seconds);
float elapsedTime = 0;
Vector3 startingPos = transform.position;
while (elapsedTime < seconds)
{
transform.position = Vector3.Lerp(startingPos, end, (elapsedTime / seconds));
elapsedTime += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
transform.position = end;
However because the for loop runs independent of time it starts the co-routine before the other one ends.
Any help on how to solve this problem would be greatly appreciated.
I have the following Unity Script
void Update()
{
Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit = new RaycastHit();
if(Physics.Raycast(mouseRay, out hit))
{
if (Input.GetMouseButtonDown(0))
{
Camera.main.GetComponent<CameraController>().ZoomIn(hit.transform.position);
}
}
}
Then in the camera I Have
public void ZoomIn(Vector3 target)
{
zoomSavePosition = camera.transform.position;
StartCoroutine(Zoom(zoomSavePosition, target, zoomDuration));
}
// ZoomRoutine
IEnumerator Zoom(Vector3 from, Vector3 to, float duration)
{
float time = 0;
Quaternion rotation = camera.transform.rotation;
Quaternion targetRotation = Quaternion.LookRotation(to - camera.transform.position);
while (time < zoomDuration)
{
camera.transform.position = Vector3.Lerp(from, to, time / zoomDuration);
camera.transform.rotation = Quaternion.Slerp(rotation, targetRotation, time / zoomDuration);
time += TimeManager.instance.deltaTime;
yield return null;
}
camera.transform.position = to;
camera.transform.rotation = targetRotation;
}
This works great, but it zoom inside the object. I would like to, no matter from where and to what, I always zoom and end up at the same distance of the target.
I was thinking to use MoveTowards but inside the coroutine this will make the movement stop, but the rotation will keep going (and it's a big ugly)
So give a point A and a point B how do I calculate a distance in the middle that is set to always the same distance from B ?
For zooming to a specific distance from the target object you need to calculate the distance from the target object in direction of the camera and then subtract it to the target position.
var heading = to - from;
var dist = heading.magnitude;
var direction = heading / dist;
Vector3 newTargetPosition = to - (direction * distance);
Ref: https://docs.unity3d.com/Manual/DirectionDistanceFromOneObjectToAnother.html
With this adjustment you can stop zooming to any provided distance.
Here is how adjusted code would look like:
public void ZoomIn(Vector3 target)
{
float targetDistance = 2f;
zoomSavePosition = camera.transform.position;
StartCoroutine(Zoom(zoomSavePosition, target, zoomDuration, targetDistance));
}
// ZoomRoutine
IEnumerator Zoom(Vector3 from, Vector3 to, float duration, float distance)
{
float time = 0;
Quaternion rotation = camera.transform.rotation;
Quaternion targetRotation = Quaternion.LookRotation(to - camera.transform.position);
var heading = to - from;
var dist = heading.magnitude;
var direction = heading / dist;
Vector3 newTargetPosition = to - (direction * distance);
while (time < zoomDuration)
{
camera.transform.position = Vector3.Lerp(from, newTargetPosition, time / zoomDuration);
camera.transform.rotation = Quaternion.Slerp(rotation, targetRotation, time / zoomDuration);
time += Time.deltaTime;
yield return null;
}
camera.transform.position = newTargetPosition;
camera.transform.rotation = targetRotation;
}
You can set value of targetDistance where you want to stop zooming.
Now it's rotating twice 360 degrees.
I want it to rotate 360 degrees but only once when clicking with the mouse button.
private void OnMouseDown()
{
if (isRotating == false)
StartCoroutine(Rotate(5));
}
And
IEnumerator Rotate(float duration)
{
Quaternion startRot = transform.rotation;
float t = 0.0f;
while (t < duration)
{
isRotating = true;
t += Time.deltaTime;
transform.rotation = startRot * Quaternion.AngleAxis(t / duration * 360f, Vector3.up);
yield return null;
}
transform.rotation = startRot;
isRotating = false;
}