Rotate object to face a direction in a duration in Unity? - unity3d

So, in my scene I have my main game object, and a second game object which serves as a reference. I want the main game object to rotate so that it will face the same way as the second game object. And this rotating needs to happen in a set time. Let's presume the duration is 3 seconds. If the second game object has a world rotation of 0, 90, 0, the main game object starts to rotate so that after three seconds it's rotation is 0, 90, 0.
So, I have the Transforms of the main game object and the second game object, now I need to calculate the amount to rotate per frame, so that the rotation happens in the correct duration.
I would very much appreciate it if someone could help me! Thank you in advance.

Whenever you want something moving within a specified time I would recommend to not do it in Update but instead use a Coroutine.
It works like a little temporary Update method and is executed each frame until the next yield. this is better to maintain and control than the Update method.
Then do your rotation using Quaternion.Lerp or also Quaternion.Slerp depending a bit on your needs
private IEnumerator RotateOverTime(Transform transformToRotate, Quaternion targetRotation, float duration)
{
var startRotation = transformToRotate.rotation;
var timePassed = 0f;
while(timePassed < duration)
{
var factor = timePassed / duration;
// optional add ease-in and -out
//factor = Mathf.SmoothStep(0, 1, factor);
transformToRotate.rotation = Quaternion.Lerp(startRotation, targetRotation, factor);
// or
//transformToRotate.rotation = Quaternion.Slerp(startRotation, targetRotation, factor);
// increae by the time passed since last frame
timePassed += time.deltaTime;
// important! This tells Unity to interrupt here, render this frame
// and continue from here in the next frame
yield return null;
}
// to be sure to end with exact values set the target rotation fix when done
transformToRotate.rotation = targetRotation;
}
Now in order to start this routine you would call it via StartCoroutine like e.g.
// simply stop any other running routine so you don't get concurrent ones
StopAllCoroutines();
StartCoroutine(transformToRotate, otherTransform.rotation, 3f);

You can use Slerp method for this. So, it should be something like this in Update:
transform.rotation = Quaternion.Slerp(transform.rotation, secondTransform.rotation, rotationSpeed * Time.deltaTime);

https://answers.unity.com/questions/765197/how-do-i-rotate-an-object-over-a-set-time.html
The answer for short:
float totalRotationTime = 5.0f;
float y_degreesPerSecond = go.transform.eulerAngles.y / totalRotationTime;
float x_degs = go.transform.x/ totalRotationTime;
//etc...
go.transform.Rotate(new Vector3(x_degs * Time.deltaTime, degreesPerSecond * Time.deltaTime, 0));

I have updated the answer given by derHugo, with this update you can define the time and the angle that object will rotate in the given time.
private IEnumerator RotateOverTime(float duration)
{
var timePassed = 0f;
while (timePassed < duration)
{
var factor = timePassed / duration;
factor = (Mathf.PI / 2) * (Time.deltaTime / duration);
print("factor: " + factor);
hedefAraYon = Vector3.RotateTowards(transform.forward, hedefYon, factor, 0.0f);
transform.rotation = Quaternion.LookRotation(hedefAraYon, Vector3.back);
// increae by the time passed since last frame
timePassed += Time.deltaTime;
// important! This tells Unity to interrupt here, render this frame
// and continue from here in the next frame
yield return null;
}
// donme takılı kalabiliyor, o yüzden 0,5f olarak ayarladım
if (Quaternion.Angle(transform.rotation, Quaternion.LookRotation(hedefYon, Vector3.back)) <= 0.5f)
{
Vector3 tempRotation = transform.rotation.eulerAngles;
tempRotation.x = Mathf.RoundToInt(tempRotation.x);
tempRotation.y = Mathf.RoundToInt(tempRotation.y);
tempRotation.z = Mathf.RoundToInt(tempRotation.z);
transform.rotation = Quaternion.Euler(tempRotation);
}
hareketDurumu = HAREKET_DURUMU.DURUYOR;
print("Time Passed-Döner OK: " + timePassedOverall);
}

Related

turning towards an object in Unity

Good times. How do I implement the NPC rotation towards the selected object?
public GameObject BufferObject;
public float MoveSpeed = 1f;
void Update()
{
float step = MoveSpeed * Time.deltaTime;
transform.position = Vector2.MoveTowards(transform.position, BufferObject.transform.position, step);
}
Here is the script for moving the NPC to the selected object (Buffer Object) and the movement works perfectly, but the implementation of the rotation causes Me difficulties, please advise.
For Unity2D.
Simply get the desired direction
var direction = (BufferObject.transform.position - transform.position).normalized;
and then the desired rotation using Quaternion.LookRotation like e.g.
var targetRotation = Quaternion.LookDirection(direction);
Then either apply it directly if you want it immediately
transform.rotation = targetRotation;
Or if you want it smooth you could use e.g. Quaternion.RotateTowards like
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, anglePerSecond * Time.deltatime);
Have in mind though that it might look awkward if the rotation is too slow since your NPC could move back/sidewards already while still not fully looking towards the target ;)
So you might want to wait until
if(Quaternion.Angle(targetRotation, transform.rotation) <= certainThreshold)
{
... your movement code
}
So the answer and the solution from Me, albeit stupid, but working. In order to reflect the sprite, you need to get a variable, either 1 or -1 (For Scale). This code will show the distance from one NPC to the object:
BufferObject.transform.position.x - transform.position.x
And here I get a lot of values as if to the left of the NPC, then -x... , and if to the right, then x... thereby it is possible to determine which side of the object, so also level the value from here (Optional) to 1 or -1 and apply this result to transform.localScale and thereby solve the problem of how to reflect (Rotate the sprite) towards the object. Use it for your health :)
Complete code:
float localPositionAmount = BufferObject.transform.position.x - transform.position.x;
if (localPositionAmount >= 1)
{
gameObject.transform.localScale = new Vector3(1, transform.localScale.y, transform.localScale.z);
}
if (localPositionAmount <= -1)
{
gameObject.transform.localScale = new Vector3(-1, transform.localScale.y, transform.localScale.z);
}
if (localPositionAmount == 0)
{
gameObject.transform.localScale = new Vector3(transform.localScale.x, transform.localScale.y, transform.localScale.z);
}
Yes, the code is the simplest and without enumerations and other things, but it works perfectly. I still had to figure it out myself...

applying velocity to rigidbody makes it float instead of dashing

Im trying to code so that my Character dashes to the right when pressing the Left mouse button, but instead of dashing it just starts slowly glieding or lets say floating.
This is the code i´ve used;
if (Input.GetMouseButton(0))
{
rb.velocity = Vector2.right * DashSpeed;
}
Im not sure but a other part of my code might be the reason for this problem but if so i would like to know how i could solve it. Thats the part im talking about
rb.velocity = new Vector2(moveInput * speed, rb.velocity.y);
thats the code im using for movement.
void Start()
{
cam = Camera.main;
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
horizontal = Input.GetAxisRaw("Horizontal");
animator.SetFloat("Horizontal", Input.GetAxis("Horizontal"));
if (Input.GetKeyDown(KeyCode.Space) && isGrounded == true)
{
float jumpVelocity = 7f;
rb.velocity = Vector2.up * jumpVelocity;
jumpsound.Play();
}
Vector3 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Input.GetKey(KeyCode.RightAlt))
{
Dashing();
}
}
void FixedUpdate()
{
isGrounded = Physics2D.OverlapCircle(groundCheck.position, CheckRadius, whatisGround);
moveInput = Input.GetAxisRaw("Horizontal");
rb.velocity = new Vector2(moveInput * speed, rb.velocity.y);
if (Input.GetKeyDown(KeyCode.Escape))
{
SceneManager.LoadScene("Main menu");
}
}
void Dashing()
{
rb.AddForce(Vector2.right * DashSpeed, ForceMode2D.Impulse);
}
The issue with your current code is you are directly changing velocity in a few places. Some basic physics, the integral of position vs. time graph is velocity, and the integral of velocity is acceleration vs. time. To get a more realistic movement, it is better to apply a Force to objects. When doing this, the physics engine Unity uses can add a new force at a given time, then using acceleration can accelerate the object in that direction over time, then can change the velocity over time which will result in the position changing over time.
The example code you posted, you are directly setting velocity in a few places.
rb.velocity = Vector2.up * jumpVelocity; (Jump)
rb.velocity = new Vector2(moveInput * speed, rb.velocity.y); (Movement)
rb.velocity = Vector2.right * DashSpeed; (Dash)
When directly setting these values, the new values overwrite the old ones. As your movement code is not in any sort of if conditional it will continually write to the velocity causing the dash to never change anything regardless if you use add-force or change velocity directly.
I would consider making both your jump and dash use AddForce, and if you like the feel of your movement by applying velocity directly, then add the velocity do not set it.
Your previous line rb.velocity = new Vector2(moveInput * speed, rb.velocity.y); would then become rb.AddForce(new Vector2(moveInput * speed, 0), ForceMode2D.Impulse);. Similarly you can update your jump and dash to match this. Let me know if you get this working or have more questions.
It could be a problem with your animation. Link to a thread on unity answers:
https://answers.unity.com/questions/674516/rigidbody-character-problems-constant-floating-jum.html
You should go over to the animation place and hit bake into pose.
You should use the Rigidbody2D.AddForce(Vector2, ForceMode2D). What this does is moving the GameObject in the direction of the Vector2, with the force mode of ForceMode2D. What is different about this from just translating it, is that it interacts with physics and improves the quality of your game. Here is a link, and the script:
https://docs.unity3d.com/ScriptReference/Rigidbody2D.AddForce.html
Rigidbody2D rb;
float dashSpeed;
void Update()
{
if (Input.GetMouseButton(0))
{
rb.AddForce(Vector2.right * dashSpeed);
}
}
And if the other part of the code you were talking about, if that is glitching, then do the same trick.

Remove delay between lerps

I made a simple script that goes to one waypoint and then to the next.
My problem is that it seems to be a delay while going from waypoint1 to waypoint2 and i don't know why:
¿Why is that delay happening and how can i remove it?
using UnityEngine;
using System.Collections;
public class Missile : MonoBehaviour
{
public Vector3 finalTarget;
public Transform forwardObject;
public GameObject impactAreaPrefab;
float smoothingDelay = 0.1f;
bool fired = false;
bool powerPhase = true;
Vector3 currentTarget;
private void OnEnable() {
fire(new Vector3(-25.29f, 0.5f, -10.638f));
}
void fire(Vector3 coords) {
currentTarget = forwardObject.position;
finalTarget = coords;
Instantiate(impactAreaPrefab, finalTarget, Quaternion.identity);
fired = true;
}
void Update() {
if (!fired) {
return;
}
if (powerPhase && transform.position == currentTarget) {
powerPhase = false;
currentTarget = finalTarget;
smoothingDelay = 0.05f;
}
transform.position = Vector3.Lerp(transform.position, currentTarget, Time.deltaTime / smoothingDelay);
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(Vector3.RotateTowards(transform.forward, currentTarget, 1, 0.0f)), Time.deltaTime / smoothingDelay);
}
}
That's happening, because you're using lerp not exactly properly. If you want to get linear movement you should cache your first argument (position/rotation on beginning) and provide increasing third parameter. This delay is happening because if your bullet is very close to final position and it's still trying to get there, but your current distance |finalPos - transform.position| is so small that your step Time.deltaTime/smoothingDelay is almost not moving it.
Vector3 startPos;
Vector3 finalPos;
float currentT = 0.0f;
void Update()
{
currentT += Time.deltaTime;
transform.position = Vector3.Lerp(startPos, finalPos, currentT);
}
Checking if Vector3 == Vector3 is also not a good idea. Use pattern from above and check if currentT is larger or equal to 1. If it's true then you're on final position. You get also some control over movement duration by dividing currentT.
So First thing read these post to get better understanding of Lerp function-
https://chicounity3d.wordpress.com/2014/05/23/how-to-lerp-like-a-pro/
http://www.kinematicsoup.com/news/2016/8/9/rrypp5tkubynjwxhxjzd42s3o034o8
You should have a better understanding of lerp now.
In summary lerp does a really simple thing. Say u have two values X and Y. For this example let us give them some value, X = 0, Y = 1, Now you want to get a value some percent between them, like u want to get a value which is 50% from X and Y. You can guess the answer is 0.5. The lerp equation for this would be
Mathf.Lerp(0, 1, 0.5f);
So simply- given two values, x and y, Mathf.Lerp returns a value that is t percent between them.
Now to properly use Lerp you need to cache the position before starting the lerp. Most times I use a coroutine to get this effect works pretty well and then u can use animation curve to change the third parameter to create some crazy good effects. For example on using a animation curve just comment i will write it.
For this problem of yours you have two options-
1) Lerp like a pro using Animation curve to manipulate the speed. Remember u can create animation curves in runtime too.
IENumerator Move(Transform toMove, Vector3 end, float duration){
Vector3 startPos = toMove.position;
float elapsed = 0f;
while(elapsed < duration){
elapsed += Time.deltaTime;
toMove.position = Vector3.Lerp(startPos, end, elapsed / duration);//manipulate the last parameter to move the object linearly
yield return null;//basically wait for next frame
}
toMove.position = end;//after lerp ends
}
Now you can instead of duration use speed and then with it you calculate the time required and change the speed to make it faster
float distance = Vector3.Distance(startPos, end);
toMove.position = Vector3.Lerp(startPos, end, elapsed / (distance/(speed * multiplier)));
2) Use Vector3.MoveTowards - This function moves a point to a end point with a given maximum step, requires three paramters, (currentPosition, end, step), u can multiply step with variable to control the speed, both work really good.
Using this is much easier in most cases
Example-
float step = speed * Time.deltaTime;//to make it framerate independent
transform.position = Vector3.MoveTowards(transform.position, end, step * multiplier);
Hope this helps. I am sorry I was unable to format my answer properly, hopefully will get better at answering. Any edits to improve the answer are welcomed :)
I recommend using iTween for smooth movement.
I modified iTween at some point for me to be able to do anything I want. like this:
public static void Rotate (Transform transform, Vector3 target, float transitionTime, Action onEnd = null, bool ignoreTimescale = false, iTween.EaseType ease = iTween.EaseType.easeInOutQuad, float delay = 0)
{
Vector3 from, to;
from = transform.localEulerAngles;
to = target;
Action <object> onUpdateAction = (rotation =>
{
transform.localEulerAngles = (Vector3) rotation;
});
Action <object> onCompleteAction = (data =>
{
if (onEnd != null)
onEnd ();
});
Hashtable hash = new Hashtable ();
hash.Add ("from", from);
hash.Add ("to", to);
hash.Add ("time", transitionTime);
hash.Add ("delay", delay);
hash.Add ("easetype", iTween.EaseType.easeInOutQuad);
hash.Add ("ignoretimescale", ignoreTimescale);
hash.Add ("onupdate", onUpdateAction);
hash.Add ("oncomplete", onCompleteAction);
iTween.ValueTo (transform.gameObject, hash);
}
That gives me full control in a variety of scenarios.
Here is the code if you want to implement it.
https://drive.google.com/open?id=1nLEEYTp-q4Kfh2n3nWQJcMXmPNtVPLLP

IsGrounded in CharacterController not stable

I have following simple scene: scaled box at (0, 0, 0) for floor and 1 size box at (0, 2, 0). I try to do simple gravity and use following code:
private float gravity = 1.0f;
private Vector3 moveVector;
void Update()
{
if (characterController.IsGrounded)
{
Debug.Log("is grounded");
verticalVelocity = 0;
}
else
{
Debug.Log("not grounded");
verticalVelocity -= gravity;
}
Debug.Log("vertical velocity:" + verticalVelocity);
moveVector.x = 0;
moveVector.y = verticalVelocity;
moveVector.z = 0;
characterController.Move(moveVector * Time.deltaTime);
}
I see in log that event when object is on the floor I have grounded/not grounded messages. Visually object is on the floor and not oscillating.
Why I don't have constant "is grounded" when object is on the floor?
Maybe it is how CharacterController works but I can't find anything about it in documentation.
You have to set Character Controller Min Move Distance to '0' (zero) or else you will always get this behavior, it is trying to limit how many times it checks if it's grounded, but at the end of the day gamers don't care, they will mash the jump button and call it buggy
Had the same issue
Just had some gravity when the character controller is grounded
Replace verticalVelocity = 0;
By
verticalVelocity = -gravity * Time.deltaTime;
Setting "Min Move Distance" to 0 in the Character Controller Inspector solved a very similar problem for me.
Strubble is right. You have to add some small gravity even if character is grounded.
if (characterController.IsGrounded)
{
// Press the character down to the floor to avoid jitter "true-false"
// of the isGrounded property.
// To do it, add some small gravity (or velocity in your terms).
verticalVelocity = -gravity * 0.1f;
}
just use a Raycast with Vector3.down because I've tired of fixing it and it's really unreliable to use even while prototyping.

Unity SmoothDamp not working as intended

Ok, I got a series of cards that will move 1 by 1 to the center of the camera each and every time the user clicks.
StageContainer is the parent of all cards. This is the one that will move making it look like the cards are moving instead.
First I this is my code without smoothdamp
// Update is called once per frame
void Update () {
if(Input.GetMouseButtonDown(0)){
StartCoroutine ( ProcessFocus() );
frames++;
}
}
IEnumerator ProcessFocus() {
curPos = StageContainer.transform.localPosition;
nextPos = curPos;
nextPosX = nextPos.x - 400;
nextPos.x = nextPosX;
StageContainer.transform.localPosition = nextPos;
yield break;
}
The code above gives me the instant change of cards on the center of the camera. No transitions, animation whatsoever.. the point is it works.
Now when I change this :
StageContainer.transform.localPosition = nextPos;
to this :
float smoothTime = 0.3F;
Vector3 velocity = Vector3.zero;
StageContainer.transform.localPosition = Vector3.SmoothDamp(curPos, nextPos, ref velocity, smoothTime);
I assumed it will transition from current X point to next X point,
but every time I mouse click, it just move bit by bit like 10~20 X points
I have no clue why it behave like that. Please help.
Your coroutine needs to keep running until your movement is completed.
IEnumerator ProcessFocus() {
curPos = StageContainer.transform.localPosition;
nextPos = curPos;
nextPosX = nextPos.x - 400;
nextPos.x = nextPosX;
float smoothTime = 0.3F;
Vector3 velocity = Vector3.zero;
while(this.transform.position != nextPos) {
StageContainer.transform.localPosition = Vector3.SmoothDamp(curPos, nextPos, ref velocity, smoothTime);
yield return null;
}
}
That's because your code runs once and then exits the coroutine. To run the coroutine for some amount of time, you have to yield return some YieldInstruction instances. In this case, I suspect that you want to use WaitForEndOfFrame.