Struggling with the implementation of character rotation animation by 180 degrees.
For clarity, I made a Pivot, which indicates the target of the direction, then compares the angle between the direction of the character and the direction of the target, calculates the difference in the nearest angles and, if the difference is greater than 179, plays the Turn180 animation.
Works, but crooked.
Screenshot
Problem:
Since the control of the character and the target are identical, the script does not have time to calculate the difference and gives out values at full turn are not always needed. Like: (-178.6661), (169.8465), (168.1936) and so on.
I'm sure there is an easier way. The only thing that comes to mind is to compare the TargetPisition with the -transform.forward and then play the reversal animation. But I can't figure out how to do it. I need help with Movement and Target.
UPDATE
I rewrote the code from scratch more carefully, but the issue with the turn remains relevant. The target and the Player are turning at the same time, how do I tell the program that I'm going to turn in the opposite direction and play the animation?
void Movement() {
// WASD
float Horizontal = Input.GetAxis("Horizontal");
float Vertical = Input.GetAxis("Vertical");
Vector3 move = Quaternion.Euler (0, playerCamera.transform.eulerAngles.y, 0) * new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
//Movement
controller.Move(move * Time.deltaTime * playerSpeed);
if (move != Vector3.zero)
{
gameObject.transform.forward = move;
}
//
//Pivot
Pivot.transform.position = gameObject.transform.position;
PivotDirection.transform.rotation = gameObject.transform.rotation;
PivotTarget.transform.position = gameObject.transform.position + move;
Vector3 PivotTargetDirection = Vector3.RotateTowards(PivotTarget.transform.forward, move, 10, 0);
PivotTarget.transform.rotation = Quaternion.LookRotation (PivotTargetDirection);
PivotArrow.transform.rotation = Quaternion.Euler (0, playerCamera.transform.eulerAngles.y, 0);
//
// ReceivedAngle
var PlayerDirection = new Vector3(0, transform.eulerAngles.y, 0);
var TargetDirection = new Vector3(0, PivotTarget.transform.eulerAngles.y, 0);
var ReceivedAngle = Mathf.DeltaAngle(PlayerDirection.y, TargetDirection.y);
print(Mathf.RoundToInt(ReceivedAngle));
//
if (ReceivedAngle >= 180)
{
animator.Play("Turn 180");
}
}
P.S. Sorry for my English.
Related
i am new in unity. I want to car game with mouse control to Unity 2D. I was trying this code but not working. Car vibrates when i move mouse over car. I want it to work perfectly when the mouse hovers over the car. how can i do this? my code is as follows:
private void OnMouseOver()
{
// Distance from camera to object. We need this to get the proper calculation.
float camDis = cam.transform.position.y - my.position.y;
// Get the mouse position in world space. Using camDis for the Z axis.
Vector3 mouse = cam.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, camDis));
float AngleRad = Mathf.Atan2 (mouse.y - my.position.y, mouse.x - my.position.x);
float angle = (180 / Mathf.PI) * AngleRad;
body.rotation = angle;
Vector3 temp = Input.mousePosition;
temp.z = 10f; // Set this to be the distance you want the object to be placed in front of the camera.
this.transform.position = Camera.main.ScreenToWorldPoint(temp);
}
I'm not too clear on the effect you want to achieve, but if you just want the object to move and turn gradually instead of instantly changing, that can be achieved using Vector3.MoveTowards and Quaternion.RotateTowards, e.g.:
private void OnMouseOver()
{
// Distance from camera to object. We need this to get the proper calculation.
float camDis = cam.transform.position.y - my.position.y;
// Get the mouse position in world space. Using camDis for the Z axis.
Vector3 mouse = cam.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, camDis));
float AngleRad = Mathf.Atan2 (mouse.y - my.position.y, mouse.x - my.position.x);
float angle = (180 / Mathf.PI) * AngleRad;
//body.rotation = angle; //??
float turnSpeed = 200f;
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(0, 0, angle), turnSpeed * Time.deltaTime);
Vector3 temp = Input.mousePosition;
temp.z = 10f; // Set this to be the distance you want the object to be placed in front of the camera.
float moveSpeed = 10f;
transform.position = Vector3.MoveTowards(transform.position, Camera.main.ScreenToWorldPoint(temp), moveSpeed * Time.deltaTime);
}
Edit in response to comment: If you want it to move only when the player begins the drag on the car, then yes, putting it in OnMouseDrag() would work. If you want it to move when the player drags from anywhere on the screen, you'd want to put the movement code in Update() and check whether the left mouse button is being held down using Input.GetMouseButton(0).
If you wanted it to keep moving towards the last mouse position (e.g. player can click on the screen and it will move there while the mouse button is not being held down), you'd need to keep the last mouse location in a class variable and move towards that in Update().
Incidentally if you want it to move a bit more like a car, you could always move it forwards while it turns towards the mouse, rather than moving it directly towards the mouse even if it's facing a different direction.
Here's an example but be aware that I've changed a few things that didn't seem necessary to me, like using my.position rather than transform.position. If you use it you may need to adapt it to suit the rest of your code.
public float maxTurnSpeed = 250f;
public float maxSpeed = 8f;
public float stopDistance = 0.5f;
public float slowDistance = 2.5f;
private void Update()
{
if( !Input.GetMouseButton(0) ) // If the mouse button is NOT being held down this frame
return; // Don't move. (Ideally you would decelerate the car rather than stopping it immediately though.)
// Remove the above two lines and move all of this to OnMouseDrag if you want to require the drag to begin on this object to move it.
// Also note: this code now assumes the object begins in the desired z position and doesn't change it, rather than forcing a z position.
// Distance from camera to object. We need this to get the proper calculation.
float camDis = transform.position.z - Camera.main.transform.position.z; // Changed this to use z instead of y as it appeared to be a mistake(?)
// Get the mouse position in world space. Using camDis for the Z axis.
Vector3 mouse = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, camDis));
float distanceFromMouse = Vector3.Distance(transform.position, mouse);
// If we're further away from the mouse than stopDistance, move.
if( distanceFromMouse > stopDistance )
{
float speedMultiplier = 1.0f;
float rotationMultiplier = 1.0f;
// If we're closer to the mouse than slowdistance, slow down proportionately to the remaining distance from stopDistance
if( distanceFromMouse < slowDistance )
{
speedMultiplier = (distanceFromMouse - stopDistance) / (slowDistance - stopDistance);
}
// Reduce turning speed as we approach stopDistance, but not by as much as speed is reduced
if( speedMultiplier < 0.5f )
rotationMultiplier = speedMultiplier * 2f;
float AngleRad = Mathf.Atan2(mouse.y - transform.position.y, mouse.x - transform.position.x);
float angle = (180 / Mathf.PI) * AngleRad;
// Turn the car towards facing the mouse position
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(0, 0, angle), maxTurnSpeed * rotationMultiplier * Time.deltaTime);
// Move the car towards its transform.right vector.
transform.position += transform.right * (maxSpeed * speedMultiplier * Time.deltaTime);
}
}
So I was looking for ways to do smooth angle motion in Unity and I stumbled upon this clip of code:
IEnumerator SlideToPosition(Vector3 targetPos, float time)
{
// Use an animation curve to make it look sweet!
AnimationCurve smoothly = AnimationCurve.EaseInOut(0, 0, 1, 1);
Transform myTrans = transform; // cache the transform for extra efficiency!
float curTime = 0;
Vector3 startPosition = myTrans.position;
moving = true;
while (curTime < time)
{
myTrans.position = Vector3.Lerp(startPosition, targetPos, smoothly.Evaluate(curTime / time));
curTime += Time.deltaTime;
yield return null;
}
moving = false;
myTrans.position = targetPos;
}
This worked absolutely fantastically and I was looking for a way to do angled motion the same way as the poster of the original method (from 6 years ago) said it would be easy to do angular movement in the same way. I've tried everything and for the life of me can't seem to get it to rotate more than a degree or so. Can anyone help me out? Here is where I'm currently at:
IEnumerator rotateToPosition(Vector3 targetAngle, float time)
{
// Use an animation curve to make it look sweet!
AnimationCurve smoothly = AnimationCurve.EaseInOut(0, 0, 1, 1);
Transform myTrans = transform; // cache the transform for extra efficiency!
float curTime = 0;
Quaternion startAngle = myTrans.rotation;
moving = true;
while (curTime < time)
{
myTrans.rotation = Quaternion.Lerp(startAngle, Quaternion.Euler(targetAngle), smoothly.Evaluate(curTime / time));
curTime += Time.deltaTime;
yield return null;
}
moving = false;
myTrans.rotation = Quaternion.Euler(targetAngle);
}
Any help would be greatly appreciated. Thanks!
The last parameter of Lerp is the time component - this is a float value between 0 and 1 which you can think of as the percentage of movement between the start and finish. i.e. a value of 0.5f would be exactly half way between (in your example) startAngle and targetAngle.
So, just do curTimefor the last parameter, as you add to it each frame with curTime += Time.deltaTime this gradually moves between 0 and 1. If you want to vary the speed of the lerp then multiply this value e.g. to make it 50% faster use curTime += Time.deltaTime * 1.5f.
Ok, so, i've been stuck on this for ages. Im working on an AI that will navigate a tank to a waypoint, defined as a Vector3. the position of the tank is also defines as a Vector3, both these have their Y position set to 0, as to ignore terrain elevation, the current rotation of the tank is also a Vector3, though only the Y rotation is needed, as i'm effectively projecting the 3d position onto a 2d navigational grid.
The AI passes anywhere between -1 and 1 into the control for the tank, which then handles the physics operations. so, i need to somehow calculate the angle, positive or negative in relation to the current heading angle of the tank to the position of the waypoint, then send the rotation value to the controls. At the moment I simply cant get it working, I feel like iv'e pretty much tried everything.
This is my code currently, it doesn't work, at all, and is about the 20th revision:
void driveToTarget()
{
Vector3 target0 = driveTarget;
target0.y = 0;
GameObject current0Obj = new GameObject();
Vector3 current0 = this.transform.position;
current0.y = 0;
print(current0);
print(target0);
Vector3 current0Angle = this.transform.eulerAngles;
print(current0Angle.y);
current0Angle.x = 0;
current0Angle.z = 0;
Vector3 heading = target0 - current0;
Quaternion headingAngle = Quaternion.LookRotation(heading);
print("headingAngle" + headingAngle);
print("heading direction, allegidly: " + Quaternion.Euler(heading).ToEulerAngles());
Quaternion distanceToGo = Quaternion.Lerp(Quaternion.Euler(current0Angle), headingAngle, 0.01f);
float angle = Vector3.SignedAngle(current0, target0, Vector3.up);
float difference = Mathf.Abs(angle - current0Angle.y);
print("heading angle " + angle);
if (current0 != driveTarget)
{
steeringVal = Mathf.Abs(1.5f-(1f/Mathf.Abs(distanceToGo.y))) * -Mathf.Sign(distanceToGo.y); ;
throttleVal = 0f;
} else
{
throttleVal = 0;
}
}
--EDIT--
So, I've partially solved it, and now encountered another problem, I've managded to get the tank to detect the angle between itself and the waypoint, BUT, rather than orienting forward towards the waypoint, the right side of the tank orients towards it, so it orbits the waypoint. I actually know why this is, becasue the forward vector of the tank is technically the right vector because of unity's stupid axis ruining my blender import, anyway, heres the updated code:
void driveToTarget()
{
Vector3 target0 = driveTarget;
target0.y = 0;
Vector3 current0 = this.transform.position;
current0.y = 0;
print("Current: " + current0);
print("Target: " + target0);
Vector3 current0Angle = this.transform.rotation.eulerAngles;
print("Curret rotation:" + current0Angle.y);
current0Angle.x = 0;
current0Angle.z = 0;
Vector3 heading = target0 - current0;
Quaternion headingAngle = Quaternion.LookRotation(heading);
print("heading angle: " + headingAngle.ToEuler());
float distanceToGo = (current0Angle.y) - headingAngle.eulerAngles.y;
print("DistanceToGo: " + distanceToGo);
if (current0 != driveTarget)
{
steeringVal = 1 * -Mathf.Sign(distanceToGo);
throttleVal = 0f;
} else
{
throttleVal = 0;
}
Debug.DrawRay(current0, heading, Color.red, 1);
Debug.DrawRay(current0, this.transform.up, Color.red, 1);
}
I'm not sure exactly how your code is setup or how the steering works. You may want to look into using the Unity NavMeshAgent to simplify this.
Regardless here is some code I wrote up that takes a destination and rotates an object towards it. All you'd have to do from there is move the object forwards.
Vector3 nextDestination = //destination;
Vector3 direction = nextDestination - transform.position;
direction = new Vector3(direction.x, 0, direction.z);
var newRotation = Quaternion.LookRotation(direction);
var finalRotation = Quaternion.Slerp(transform.rotation, newRotation, Time.deltaTime); //smoothes out rotation
transform.rotation = finalRotation;
Sorry if this isn't what you needed. Have you been able to figure out which part of the code is behaving unexpectedly from your print statements?
I am trying to automate a random rotation of a 3rd person character in Unity and I'd like the rotation to be animated as if I were turning the player myself using the out of the box controller and the WASD keys. The goal is an NPC that randomly rotates, looking for someone in a crowd.
Here is what I've attempted thus far, within a coroutine in update.
float xDegress = Random.Range(10f, 75f);
float yDegress = Random.Range(10f, 75f);
float zDegress = Random.Range(10f, 75f);
// Works but immediate
this.transform.Rotate(0f, yDegress, 0f);
// Works but immediate
this.transform.LookAt(new Vector3(xDegress, 0f, zDegress));
// Doesn't work
rB.rotation = Quaternion.Lerp(transform.rotation, new Quaternion(0, yDegress, 0, 0), 0.5f);
// Doesn't work
rB.MoveRotation(new Quaternion(0, yDegress, 0, 1).normalized);
// Works but immediate
Vector3 newPosition = new Vector3(xDegress, 0f, zDegress);
transform.rotation = Quaternion.Lerp(
transform.rotation,
Quaternion.LookRotation(newPosition, Vector3.up),
0.5f);
yield return new WaitForSeconds(_pauseSeconds);
Here's the inspector for the NPC, which is Ethan from the standard assets.
First of all since it is a RigidBody you shouldn't use transform.rotation = nor any transform. method at all but stick to rb.MoveRotation or rb.rotation.
Than you used Lerp in a "wrong" way. What you want is a value that grows from 0 to 1. Calling it only once with a factor of fixed 0.5f allways results in a rotation halfway inbetween the first and the second
one - immediately.
First you need a desired duration for the rotation.
// How long in seconds should the rotation take?
// might be a constant or set via the inspector
public float rotationDuration = 1.0f;
Than before you start rotating get the start and end rotation
float xDegress = Random.Range(10f, 75f);
float yDegress = Random.Range(10f, 75f);
float zDegress = Random.Range(10f, 75f);
var currentRotation = transform.rotation;
var targetRotation = currentRotation * Quaternion.Euler(xDegress, yDegress, zDegress);
// or
Vector3 newPosition = new Vector3(xDegress, 0f, zDegress);
var targetRotation = Quaternion.LookRotation(newPosition, Vector3.up);
// or whatever your target rotation shall be
Now since you already are in a Coroutine you can simply add a while loop performing the rotation within the desired duration (don't forget the yield though)
var passedTime = 1.0f;
while(passedTime < rotationDuration)
{
// interpolate the rotation between the original and the targetRotation
// factor is a value between 0 and 1
rb.MoveRotation = Quaternion.Lerp(currentRotation, targetRotation, passedTime / rotationDuration);
// add time passed since last frame
passedtime += Time.deltaTime;
yield return null;
}
// to avoid overshooting set the target rotation fix when done
transform.rotation = targetRotation;
yield return new WaitForSeconds(_pauseSeconds);
This is rather bizarre; and I have no idea how to actually fix it.
I use a set of limit (min and max), to know how much the scrollwheel can zoom in or out a camera. The problem is that the camera get stuck, because I specify that the zoom should happen only if the y position is between min and max.
Unity always overshoot when using the mouse scrollwheel; if I set 1.0f, and scroll; the final result end up being 0.92; and same goes if I set a max of 5.0f, and using the scrollwheel, it overshoot to 5.1.
This result in the camera getting stuck, because now the camera y value is set beyond the limits, so obviously it won't move.
How do you actually avoid that Unity will go beyond the actual limits?
This is what I use
float mouse = Input.GetAxis("Mouse ScrollWheel");
float zoom_speed = 1.0f;
if (transform.position.y <= 3f && transform.position.y >= 1f)
transform.Translate(0, -mouse * zoom_speed, mouse * zoom_speed, Space.World);
So, this code makes your transform travel between minZoomPos and maxZoomPos in world space. The currentZoom is the normalized value that determines how close is transform to minZoomPos and how var from maxZoomPos:
Vector3 minZoomPos = new Vector3(0, 1f, -3f); //transform's position when zoomed in all the way
Vector3 maxZoomPos = new Vector3(0, 3f, -1f); //position when zoomed out
float currentZoom = 0f; //this is normalized value >> 0 means it's zoomed all the way in, 1 means it's on all the way out
float zoom_speed = 1.0f; //if you keep these declarations in Update() function it will give you unnecessary overhead on garbage collection
void Update(){
currentZoom += Input.GetAxis("Mouse ScrollWheel") * zoom_speed;
currentZoom = Mathf.Clamp01(currentZoom);
//get point between minZoomPos and maxZoomPos depending on currentZoom
transform.position = Vector3.Lerp (minZoomPos, maxZoomPos, currentZoom);
}
Found a solution.
Basically if I go below or above the limit; I just set the limit by hand. Not elegant, but with 4 lines of code I get what I need.
I also did change how I handle the zoom; since I don't need to move on both Y and Z axis, I just lower the camera on Y axis, which works better for my scenario
float mouse = Input.GetAxis("Mouse ScrollWheel");
float zoom_speed = 1.0f;
if (transform.position.y <= 3f && transform.position.y >= 1f)
transform.Translate(0, -mouse * zoom_speed, 0);
if (transform.position.y < 1f)
transform.position = new Vector3(transform.position.x, 1f, transform.position.z)
if (transform.position.y > 3f)
transform.position = new Vector3(transform.position.x, 3f, transform.position.z)
With this, the value get tweaked as soon as it is changed, it happens so fast that the camera does not jitter or even move slightly. Pretty sure there are better ways to do so, but this seems to work for now.